From dc85623c308bc4b4c6c8a7c23d47ef80c950ab21 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 18 Aug 2017 15:13:04 -0700 Subject: [PATCH 001/172] Fix #17069 and #15371 1. `T[K]` now correctly produces `number` when `K extends string, T extends Record`. 2. `T[K]` no longer allows any type to be assigned to it when `T extends object, K extends keyof T`. Previously both of these cases failed in getConstraintOfIndexedAccessType because both bases followed `K`'s base constraint to `string` and then incorrectly produced `any` for types (like `object`) with no string index signature. In (1), this produced an error in checkBinaryLikeExpression`. In (2), this failed to produce an error in `checkTypeRelatedTo`. --- src/compiler/checker.ts | 19 ++++++++++++------- src/compiler/core.ts | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6df6b1f9ce1..ec67df6b80d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5911,7 +5911,14 @@ namespace ts { return transformed; } const baseObjectType = getBaseConstraintOfType(type.objectType); - const baseIndexType = getBaseConstraintOfType(type.indexType); + const keepTypeParameterForMappedType = baseObjectType && getObjectFlags(baseObjectType) & ObjectFlags.Mapped && + type.indexType.flags & TypeFlags.TypeParameter; + const baseIndexType = !keepTypeParameterForMappedType && getBaseConstraintOfType(type.indexType); + if (baseObjectType && baseIndexType === stringType && !getIndexInfoOfType(baseObjectType, IndexKind.String)) { + // getIndexedAccessType returns `any` for X[string] where X doesn't have an index signature. + // to avoid this, return `undefined`. + return undefined; + } return baseObjectType || baseIndexType ? getIndexedAccessType(baseObjectType || type.objectType, baseIndexType || type.indexType) : undefined; } @@ -5961,8 +5968,9 @@ namespace ts { function computeBaseConstraint(t: Type): Type { if (t.flags & TypeFlags.TypeParameter) { const constraint = getConstraintFromTypeParameter(t); - return (t).isThisType ? constraint : - constraint ? getBaseConstraint(constraint) : undefined; + return (t as TypeParameter).isThisType || !constraint ? + constraint : + getBaseConstraint(constraint); } if (t.flags & TypeFlags.UnionOrIntersection) { const types = (t).types; @@ -5990,9 +5998,6 @@ namespace ts { const baseIndexedAccess = baseObjectType && baseIndexType ? getIndexedAccessType(baseObjectType, baseIndexType) : undefined; return baseIndexedAccess && baseIndexedAccess !== unknownType ? getBaseConstraint(baseIndexedAccess) : undefined; } - if (isGenericMappedType(t)) { - return emptyObjectType; - } return t; } } @@ -9289,7 +9294,7 @@ namespace ts { } else if (target.flags & TypeFlags.IndexedAccess) { // A type S is related to a type T[K] if S is related to A[K], where K is string-like and - // A is the apparent type of S. + // A is the apparent type of T. const constraint = getConstraintOfType(target); if (constraint) { if (result = isRelatedTo(source, constraint, reportErrors)) { diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 8cab1b41155..422df2ead05 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -718,7 +718,7 @@ namespace ts { export function sum, K extends string>(array: T[], prop: K): number { let result = 0; for (const v of array) { - // Note: we need the following type assertion because of GH #17069 + // TODO: Remove the following type assertion once the fix for #17069 is merged result += v[prop] as number; } return result; From ecae295c5a70ebe1be2c13119a818a8bfabfbc1b Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 18 Aug 2017 15:16:24 -0700 Subject: [PATCH 002/172] Test getConstraintOfIndexedAccess fixes and update baselines --- ...ionOperatorWithConstrainedTypeParameter.js | 27 ++++++++ ...eratorWithConstrainedTypeParameter.symbols | 54 +++++++++++++++ ...OperatorWithConstrainedTypeParameter.types | 64 ++++++++++++++++++ ...tiveConstraintOfIndexAccessType.errors.txt | 67 +++++++++++++++++++ ...nonPrimitiveConstraintOfIndexAccessType.js | 67 +++++++++++++++++++ ...ionOperatorWithConstrainedTypeParameter.ts | 11 +++ ...nonPrimitiveConstraintOfIndexAccessType.ts | 32 +++++++++ 7 files changed, 322 insertions(+) create mode 100644 tests/baselines/reference/additionOperatorWithConstrainedTypeParameter.js create mode 100644 tests/baselines/reference/additionOperatorWithConstrainedTypeParameter.symbols create mode 100644 tests/baselines/reference/additionOperatorWithConstrainedTypeParameter.types create mode 100644 tests/baselines/reference/nonPrimitiveConstraintOfIndexAccessType.errors.txt create mode 100644 tests/baselines/reference/nonPrimitiveConstraintOfIndexAccessType.js create mode 100644 tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithConstrainedTypeParameter.ts create mode 100644 tests/cases/conformance/types/nonPrimitive/nonPrimitiveConstraintOfIndexAccessType.ts diff --git a/tests/baselines/reference/additionOperatorWithConstrainedTypeParameter.js b/tests/baselines/reference/additionOperatorWithConstrainedTypeParameter.js new file mode 100644 index 00000000000..1366e242182 --- /dev/null +++ b/tests/baselines/reference/additionOperatorWithConstrainedTypeParameter.js @@ -0,0 +1,27 @@ +//// [additionOperatorWithConstrainedTypeParameter.ts] +// test for #17069 +function sum, K extends string>(n: number, v: T, k: K) { + n = n + v[k]; + n += v[k]; // += should work the same way +} +function realSum, K extends string>(n: number, vs: T[], k: K) { + for (const v of vs) { + n = n + v[k]; + n += v[k]; + } +} + + +//// [additionOperatorWithConstrainedTypeParameter.js] +// test for #17069 +function sum(n, v, k) { + n = n + v[k]; + n += v[k]; // += should work the same way +} +function realSum(n, vs, k) { + for (var _i = 0, vs_1 = vs; _i < vs_1.length; _i++) { + var v = vs_1[_i]; + n = n + v[k]; + n += v[k]; + } +} diff --git a/tests/baselines/reference/additionOperatorWithConstrainedTypeParameter.symbols b/tests/baselines/reference/additionOperatorWithConstrainedTypeParameter.symbols new file mode 100644 index 00000000000..e7055c1e38f --- /dev/null +++ b/tests/baselines/reference/additionOperatorWithConstrainedTypeParameter.symbols @@ -0,0 +1,54 @@ +=== tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithConstrainedTypeParameter.ts === +// test for #17069 +function sum, K extends string>(n: number, v: T, k: K) { +>sum : Symbol(sum, Decl(additionOperatorWithConstrainedTypeParameter.ts, 0, 0)) +>T : Symbol(T, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 13)) +>Record : Symbol(Record, Decl(lib.d.ts, --, --)) +>K : Symbol(K, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 41)) +>K : Symbol(K, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 41)) +>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 60)) +>v : Symbol(v, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 70)) +>T : Symbol(T, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 13)) +>k : Symbol(k, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 76)) +>K : Symbol(K, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 41)) + + n = n + v[k]; +>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 60)) +>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 60)) +>v : Symbol(v, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 70)) +>k : Symbol(k, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 76)) + + n += v[k]; // += should work the same way +>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 60)) +>v : Symbol(v, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 70)) +>k : Symbol(k, Decl(additionOperatorWithConstrainedTypeParameter.ts, 1, 76)) +} +function realSum, K extends string>(n: number, vs: T[], k: K) { +>realSum : Symbol(realSum, Decl(additionOperatorWithConstrainedTypeParameter.ts, 4, 1)) +>T : Symbol(T, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 17)) +>Record : Symbol(Record, Decl(lib.d.ts, --, --)) +>K : Symbol(K, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 45)) +>K : Symbol(K, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 45)) +>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 64)) +>vs : Symbol(vs, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 74)) +>T : Symbol(T, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 17)) +>k : Symbol(k, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 83)) +>K : Symbol(K, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 45)) + + for (const v of vs) { +>v : Symbol(v, Decl(additionOperatorWithConstrainedTypeParameter.ts, 6, 14)) +>vs : Symbol(vs, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 74)) + + n = n + v[k]; +>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 64)) +>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 64)) +>v : Symbol(v, Decl(additionOperatorWithConstrainedTypeParameter.ts, 6, 14)) +>k : Symbol(k, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 83)) + + n += v[k]; +>n : Symbol(n, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 64)) +>v : Symbol(v, Decl(additionOperatorWithConstrainedTypeParameter.ts, 6, 14)) +>k : Symbol(k, Decl(additionOperatorWithConstrainedTypeParameter.ts, 5, 83)) + } +} + diff --git a/tests/baselines/reference/additionOperatorWithConstrainedTypeParameter.types b/tests/baselines/reference/additionOperatorWithConstrainedTypeParameter.types new file mode 100644 index 00000000000..d52c77a94fd --- /dev/null +++ b/tests/baselines/reference/additionOperatorWithConstrainedTypeParameter.types @@ -0,0 +1,64 @@ +=== tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithConstrainedTypeParameter.ts === +// test for #17069 +function sum, K extends string>(n: number, v: T, k: K) { +>sum : , K extends string>(n: number, v: T, k: K) => void +>T : T +>Record : Record +>K : K +>K : K +>n : number +>v : T +>T : T +>k : K +>K : K + + n = n + v[k]; +>n = n + v[k] : number +>n : number +>n + v[k] : number +>n : number +>v[k] : T[K] +>v : T +>k : K + + n += v[k]; // += should work the same way +>n += v[k] : number +>n : number +>v[k] : T[K] +>v : T +>k : K +} +function realSum, K extends string>(n: number, vs: T[], k: K) { +>realSum : , K extends string>(n: number, vs: T[], k: K) => void +>T : T +>Record : Record +>K : K +>K : K +>n : number +>vs : T[] +>T : T +>k : K +>K : K + + for (const v of vs) { +>v : T +>vs : T[] + + n = n + v[k]; +>n = n + v[k] : number +>n : number +>n + v[k] : number +>n : number +>v[k] : T[K] +>v : T +>k : K + + n += v[k]; +>n += v[k] : number +>n : number +>v[k] : T[K] +>v : T +>k : K + } +} + diff --git a/tests/baselines/reference/nonPrimitiveConstraintOfIndexAccessType.errors.txt b/tests/baselines/reference/nonPrimitiveConstraintOfIndexAccessType.errors.txt new file mode 100644 index 00000000000..56781157131 --- /dev/null +++ b/tests/baselines/reference/nonPrimitiveConstraintOfIndexAccessType.errors.txt @@ -0,0 +1,67 @@ +tests/cases/conformance/types/nonPrimitive/nonPrimitiveConstraintOfIndexAccessType.ts(3,5): error TS2322: Type 'string' is not assignable to type 'T[P]'. +tests/cases/conformance/types/nonPrimitive/nonPrimitiveConstraintOfIndexAccessType.ts(6,5): error TS2322: Type 'string' is not assignable to type 'T[P]'. +tests/cases/conformance/types/nonPrimitive/nonPrimitiveConstraintOfIndexAccessType.ts(9,5): error TS2322: Type 'string' is not assignable to type 'T[P]'. +tests/cases/conformance/types/nonPrimitive/nonPrimitiveConstraintOfIndexAccessType.ts(12,5): error TS2322: Type 'string' is not assignable to type 'T[P]'. +tests/cases/conformance/types/nonPrimitive/nonPrimitiveConstraintOfIndexAccessType.ts(15,5): error TS2322: Type 'string' is not assignable to type 'T[P]'. +tests/cases/conformance/types/nonPrimitive/nonPrimitiveConstraintOfIndexAccessType.ts(18,5): error TS2322: Type 'string' is not assignable to type 'T[P]'. +tests/cases/conformance/types/nonPrimitive/nonPrimitiveConstraintOfIndexAccessType.ts(21,5): error TS2322: Type 'string' is not assignable to type 'T[P]'. +tests/cases/conformance/types/nonPrimitive/nonPrimitiveConstraintOfIndexAccessType.ts(24,5): error TS2322: Type 'string' is not assignable to type 'T[P]'. +tests/cases/conformance/types/nonPrimitive/nonPrimitiveConstraintOfIndexAccessType.ts(27,5): error TS2322: Type 'string' is not assignable to type 'T[P]'. +tests/cases/conformance/types/nonPrimitive/nonPrimitiveConstraintOfIndexAccessType.ts(30,5): error TS2322: Type 'string' is not assignable to type 'T[P]'. + Type 'string' is not assignable to type 'number'. + + +==== tests/cases/conformance/types/nonPrimitive/nonPrimitiveConstraintOfIndexAccessType.ts (10 errors) ==== + // test for #15371 + function f(s: string, tp: T[P]): void { + tp = s; + ~~ +!!! error TS2322: Type 'string' is not assignable to type 'T[P]'. + } + function g(s: string, tp: T[P]): void { + tp = s; + ~~ +!!! error TS2322: Type 'string' is not assignable to type 'T[P]'. + } + function h(s: string, tp: T[P]): void { + tp = s; + ~~ +!!! error TS2322: Type 'string' is not assignable to type 'T[P]'. + } + function i(s: string, tp: T[P]): void { + tp = s; + ~~ +!!! error TS2322: Type 'string' is not assignable to type 'T[P]'. + } + function j(s: string, tp: T[P]): void { + tp = s; + ~~ +!!! error TS2322: Type 'string' is not assignable to type 'T[P]'. + } + function k(s: string, tp: T[P]): void { + tp = s; + ~~ +!!! error TS2322: Type 'string' is not assignable to type 'T[P]'. + } + function o(s: string, tp: T[P]): void { + tp = s; + ~~ +!!! error TS2322: Type 'string' is not assignable to type 'T[P]'. + } + function l(s: string, tp: T[P]): void { + tp = s; + ~~ +!!! error TS2322: Type 'string' is not assignable to type 'T[P]'. + } + function m(s: string, tp: T[P]): void { + tp = s; + ~~ +!!! error TS2322: Type 'string' is not assignable to type 'T[P]'. + } + function n(s: string, tp: T[P]): void { + tp = s; + ~~ +!!! error TS2322: Type 'string' is not assignable to type 'T[P]'. +!!! error TS2322: Type 'string' is not assignable to type 'number'. + } + \ No newline at end of file diff --git a/tests/baselines/reference/nonPrimitiveConstraintOfIndexAccessType.js b/tests/baselines/reference/nonPrimitiveConstraintOfIndexAccessType.js new file mode 100644 index 00000000000..7a24345a94b --- /dev/null +++ b/tests/baselines/reference/nonPrimitiveConstraintOfIndexAccessType.js @@ -0,0 +1,67 @@ +//// [nonPrimitiveConstraintOfIndexAccessType.ts] +// test for #15371 +function f(s: string, tp: T[P]): void { + tp = s; +} +function g(s: string, tp: T[P]): void { + tp = s; +} +function h(s: string, tp: T[P]): void { + tp = s; +} +function i(s: string, tp: T[P]): void { + tp = s; +} +function j(s: string, tp: T[P]): void { + tp = s; +} +function k(s: string, tp: T[P]): void { + tp = s; +} +function o(s: string, tp: T[P]): void { + tp = s; +} +function l(s: string, tp: T[P]): void { + tp = s; +} +function m(s: string, tp: T[P]): void { + tp = s; +} +function n(s: string, tp: T[P]): void { + tp = s; +} + + +//// [nonPrimitiveConstraintOfIndexAccessType.js] +"use strict"; +// test for #15371 +function f(s, tp) { + tp = s; +} +function g(s, tp) { + tp = s; +} +function h(s, tp) { + tp = s; +} +function i(s, tp) { + tp = s; +} +function j(s, tp) { + tp = s; +} +function k(s, tp) { + tp = s; +} +function o(s, tp) { + tp = s; +} +function l(s, tp) { + tp = s; +} +function m(s, tp) { + tp = s; +} +function n(s, tp) { + tp = s; +} diff --git a/tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithConstrainedTypeParameter.ts b/tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithConstrainedTypeParameter.ts new file mode 100644 index 00000000000..2303bb33944 --- /dev/null +++ b/tests/cases/conformance/expressions/binaryOperators/additionOperator/additionOperatorWithConstrainedTypeParameter.ts @@ -0,0 +1,11 @@ +// test for #17069 +function sum, K extends string>(n: number, v: T, k: K) { + n = n + v[k]; + n += v[k]; // += should work the same way +} +function realSum, K extends string>(n: number, vs: T[], k: K) { + for (const v of vs) { + n = n + v[k]; + n += v[k]; + } +} diff --git a/tests/cases/conformance/types/nonPrimitive/nonPrimitiveConstraintOfIndexAccessType.ts b/tests/cases/conformance/types/nonPrimitive/nonPrimitiveConstraintOfIndexAccessType.ts new file mode 100644 index 00000000000..1e852f12f92 --- /dev/null +++ b/tests/cases/conformance/types/nonPrimitive/nonPrimitiveConstraintOfIndexAccessType.ts @@ -0,0 +1,32 @@ +// @strict: true +// test for #15371 +function f(s: string, tp: T[P]): void { + tp = s; +} +function g(s: string, tp: T[P]): void { + tp = s; +} +function h(s: string, tp: T[P]): void { + tp = s; +} +function i(s: string, tp: T[P]): void { + tp = s; +} +function j(s: string, tp: T[P]): void { + tp = s; +} +function k(s: string, tp: T[P]): void { + tp = s; +} +function o(s: string, tp: T[P]): void { + tp = s; +} +function l(s: string, tp: T[P]): void { + tp = s; +} +function m(s: string, tp: T[P]): void { + tp = s; +} +function n(s: string, tp: T[P]): void { + tp = s; +} From 04339d2df7c3483bcbc44a92a8e4840274aaef02 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 28 Aug 2017 09:05:56 -0700 Subject: [PATCH 003/172] Disallow T[K] = T[keyof T] where K extends keyof T `K = keyof T` was already correctly disallowed. --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8e34b24cf1b..27b0752f53f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5914,7 +5914,7 @@ namespace ts { const keepTypeParameterForMappedType = baseObjectType && getObjectFlags(baseObjectType) & ObjectFlags.Mapped && type.indexType.flags & TypeFlags.TypeParameter; const baseIndexType = !keepTypeParameterForMappedType && getBaseConstraintOfType(type.indexType); - if (baseObjectType && baseIndexType === stringType && !getIndexInfoOfType(baseObjectType, IndexKind.String)) { + if (baseIndexType === stringType && (!baseObjectType || !getIndexInfoOfType(baseObjectType, IndexKind.String))) { // getIndexedAccessType returns `any` for X[string] where X doesn't have an index signature. // to avoid this, return `undefined`. return undefined; From 20e579847a086119e309906e6811508cf08d7287 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 28 Aug 2017 09:12:14 -0700 Subject: [PATCH 004/172] Test:T[keyof T] =/=> T[K] where K extends keyof T --- .../indexedAccessRelation2.errors.txt | 18 ++++++++++++++++++ .../reference/indexedAccessRelation2.js | 19 +++++++++++++++++++ .../cases/compiler/indexedAccessRelation2.ts | 8 ++++++++ 3 files changed, 45 insertions(+) create mode 100644 tests/baselines/reference/indexedAccessRelation2.errors.txt create mode 100644 tests/baselines/reference/indexedAccessRelation2.js create mode 100644 tests/cases/compiler/indexedAccessRelation2.ts diff --git a/tests/baselines/reference/indexedAccessRelation2.errors.txt b/tests/baselines/reference/indexedAccessRelation2.errors.txt new file mode 100644 index 00000000000..b208e92250f --- /dev/null +++ b/tests/baselines/reference/indexedAccessRelation2.errors.txt @@ -0,0 +1,18 @@ +tests/cases/compiler/indexedAccessRelation2.ts(4,9): error TS2322: Type 'keyof T' is not assignable to type 'K'. +tests/cases/compiler/indexedAccessRelation2.ts(5,9): error TS2322: Type 'T[keyof T]' is not assignable to type 'T[K]'. + + +==== tests/cases/compiler/indexedAccessRelation2.ts (2 errors) ==== + // Repro from #17166 + function f(obj: T, k: K, value: T[K]): void { + for (let key in obj) { + k = key // error, keyof T =/=> K + ~ +!!! error TS2322: Type 'keyof T' is not assignable to type 'K'. + value = obj[key]; // error, T[keyof T] =/=> T[K] + ~~~~~ +!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'T[K]'. + } + } + + \ No newline at end of file diff --git a/tests/baselines/reference/indexedAccessRelation2.js b/tests/baselines/reference/indexedAccessRelation2.js new file mode 100644 index 00000000000..39b093ef3f7 --- /dev/null +++ b/tests/baselines/reference/indexedAccessRelation2.js @@ -0,0 +1,19 @@ +//// [indexedAccessRelation2.ts] +// Repro from #17166 +function f(obj: T, k: K, value: T[K]): void { + for (let key in obj) { + k = key // error, keyof T =/=> K + value = obj[key]; // error, T[keyof T] =/=> T[K] + } +} + + + +//// [indexedAccessRelation2.js] +// Repro from #17166 +function f(obj, k, value) { + for (var key in obj) { + k = key; // error, keyof T =/=> K + value = obj[key]; // error, T[keyof T] =/=> T[K] + } +} diff --git a/tests/cases/compiler/indexedAccessRelation2.ts b/tests/cases/compiler/indexedAccessRelation2.ts new file mode 100644 index 00000000000..736651099dc --- /dev/null +++ b/tests/cases/compiler/indexedAccessRelation2.ts @@ -0,0 +1,8 @@ +// Repro from #17166 +function f(obj: T, k: K, value: T[K]): void { + for (let key in obj) { + k = key // error, keyof T =/=> K + value = obj[key]; // error, T[keyof T] =/=> T[K] + } +} + From 2f646daf0ab38c10e2149507f2497204f2eb09f9 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 28 Aug 2017 10:53:11 -0700 Subject: [PATCH 005/172] Move lone test case into central test file --- .../indexedAccessRelation2.errors.txt | 18 ----------------- .../reference/indexedAccessRelation2.js | 19 ------------------ .../keyofAndIndexedAccessErrors.errors.txt | 20 +++++++++++++++++-- .../reference/keyofAndIndexedAccessErrors.js | 19 +++++++++++++++++- .../cases/compiler/indexedAccessRelation2.ts | 8 -------- .../keyof/keyofAndIndexedAccessErrors.ts | 11 +++++++++- 6 files changed, 46 insertions(+), 49 deletions(-) delete mode 100644 tests/baselines/reference/indexedAccessRelation2.errors.txt delete mode 100644 tests/baselines/reference/indexedAccessRelation2.js delete mode 100644 tests/cases/compiler/indexedAccessRelation2.ts diff --git a/tests/baselines/reference/indexedAccessRelation2.errors.txt b/tests/baselines/reference/indexedAccessRelation2.errors.txt deleted file mode 100644 index b208e92250f..00000000000 --- a/tests/baselines/reference/indexedAccessRelation2.errors.txt +++ /dev/null @@ -1,18 +0,0 @@ -tests/cases/compiler/indexedAccessRelation2.ts(4,9): error TS2322: Type 'keyof T' is not assignable to type 'K'. -tests/cases/compiler/indexedAccessRelation2.ts(5,9): error TS2322: Type 'T[keyof T]' is not assignable to type 'T[K]'. - - -==== tests/cases/compiler/indexedAccessRelation2.ts (2 errors) ==== - // Repro from #17166 - function f(obj: T, k: K, value: T[K]): void { - for (let key in obj) { - k = key // error, keyof T =/=> K - ~ -!!! error TS2322: Type 'keyof T' is not assignable to type 'K'. - value = obj[key]; // error, T[keyof T] =/=> T[K] - ~~~~~ -!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'T[K]'. - } - } - - \ No newline at end of file diff --git a/tests/baselines/reference/indexedAccessRelation2.js b/tests/baselines/reference/indexedAccessRelation2.js deleted file mode 100644 index 39b093ef3f7..00000000000 --- a/tests/baselines/reference/indexedAccessRelation2.js +++ /dev/null @@ -1,19 +0,0 @@ -//// [indexedAccessRelation2.ts] -// Repro from #17166 -function f(obj: T, k: K, value: T[K]): void { - for (let key in obj) { - k = key // error, keyof T =/=> K - value = obj[key]; // error, T[keyof T] =/=> T[K] - } -} - - - -//// [indexedAccessRelation2.js] -// Repro from #17166 -function f(obj, k, value) { - for (var key in obj) { - k = key; // error, keyof T =/=> K - value = obj[key]; // error, T[keyof T] =/=> T[K] - } -} diff --git a/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt b/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt index 0634c3419e0..2981cb0366d 100644 --- a/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt +++ b/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt @@ -27,9 +27,11 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(76,5): error Type 'T' is not assignable to type 'T & U'. Type 'T' is not assignable to type 'U'. tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(77,5): error TS2322: Type 'keyof (T & U)' is not assignable to type 'keyof (T | U)'. +tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(84,9): error TS2322: Type 'keyof T' is not assignable to type 'K'. +tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(85,9): error TS2322: Type 'T[keyof T]' is not assignable to type 'T[K]'. -==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (25 errors) ==== +==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (27 errors) ==== class Shape { name: string; width: number; @@ -162,4 +164,18 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(77,5): error ~~ !!! error TS2322: Type 'keyof (T & U)' is not assignable to type 'keyof (T | U)'. k2 = k1; - } \ No newline at end of file + } + + // Repro from #17166 + function f3(obj: T, k: K, value: T[K]): void { + for (let key in obj) { + k = key // error, keyof T =/=> K + ~ +!!! error TS2322: Type 'keyof T' is not assignable to type 'K'. + value = obj[key]; // error, T[keyof T] =/=> T[K] + ~~~~~ +!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'T[K]'. + } + } + + \ No newline at end of file diff --git a/tests/baselines/reference/keyofAndIndexedAccessErrors.js b/tests/baselines/reference/keyofAndIndexedAccessErrors.js index 838a3a6a86b..2a1042cf4ee 100644 --- a/tests/baselines/reference/keyofAndIndexedAccessErrors.js +++ b/tests/baselines/reference/keyofAndIndexedAccessErrors.js @@ -77,7 +77,17 @@ function f20(k1: keyof (T | U), k2: keyof (T & U), o1: T | U, o2: T & U) { o2 = o1; // Error k1 = k2; // Error k2 = k1; -} +} + +// Repro from #17166 +function f3(obj: T, k: K, value: T[K]): void { + for (let key in obj) { + k = key // error, keyof T =/=> K + value = obj[key]; // error, T[keyof T] =/=> T[K] + } +} + + //// [keyofAndIndexedAccessErrors.js] var Shape = /** @class */ (function () { @@ -109,3 +119,10 @@ function f20(k1, k2, o1, o2) { k1 = k2; // Error k2 = k1; } +// Repro from #17166 +function f3(obj, k, value) { + for (var key in obj) { + k = key; // error, keyof T =/=> K + value = obj[key]; // error, T[keyof T] =/=> T[K] + } +} diff --git a/tests/cases/compiler/indexedAccessRelation2.ts b/tests/cases/compiler/indexedAccessRelation2.ts deleted file mode 100644 index 736651099dc..00000000000 --- a/tests/cases/compiler/indexedAccessRelation2.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Repro from #17166 -function f(obj: T, k: K, value: T[K]): void { - for (let key in obj) { - k = key // error, keyof T =/=> K - value = obj[key]; // error, T[keyof T] =/=> T[K] - } -} - diff --git a/tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts b/tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts index cbbc9a52200..ffc5076831a 100644 --- a/tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts +++ b/tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts @@ -76,4 +76,13 @@ function f20(k1: keyof (T | U), k2: keyof (T & U), o1: T | U, o2: T & U) { o2 = o1; // Error k1 = k2; // Error k2 = k1; -} \ No newline at end of file +} + +// Repro from #17166 +function f3(obj: T, k: K, value: T[K]): void { + for (let key in obj) { + k = key // error, keyof T =/=> K + value = obj[key]; // error, T[keyof T] =/=> T[K] + } +} + From a06f0c3d9f97a99fb84cd7ee15433c2fe37655b8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 18 Oct 2017 17:15:02 -0700 Subject: [PATCH 006/172] Use builder state to emit instead --- src/compiler/builder.ts | 513 +++++++++--------- src/compiler/tsc.ts | 4 +- src/compiler/watch.ts | 40 +- src/harness/unittests/builder.ts | 9 +- src/server/project.ts | 27 +- .../reference/api/tsserverlibrary.d.ts | 67 ++- tests/baselines/reference/api/typescript.d.ts | 64 +++ 7 files changed, 408 insertions(+), 316 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 34ca1bdf0e9..9a60557ae36 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -11,10 +11,8 @@ namespace ts { writeByteOrderMark: boolean; text: string; } -} -/* @internal */ -namespace ts { + /* @internal */ export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput { const outputFiles: OutputFile[] = []; @@ -26,213 +24,270 @@ namespace ts { } } - export interface Builder { - /** Called to inform builder about new program */ - updateProgram(newProgram: Program): void; - - /** Gets the files affected by the file path */ - getFilesAffectedBy(program: Program, path: Path): ReadonlyArray; - - /** Emit the changed files and clear the cache of the changed files */ - emitChangedFiles(program: Program, writeFileCallback: WriteFileCallback): ReadonlyArray; - - /** When called gets the semantic diagnostics for the program. It also caches the diagnostics and manage them */ - getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): ReadonlyArray; - - /** Called to reset the status of the builder */ - clear(): void; + function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined) { + if (map1 === undefined) { + return map2 === undefined; + } + if (map2 === undefined) { + return map1 === undefined; + } + // Has same size and every key is present in both maps + return map1.size === map2.size && !forEachEntry(map1, (_value, key) => !map2.has(key)); } - interface EmitHandler { + /** + * State on which you can query affected files (files to save) and get semantic diagnostics(with their cache managed in the object) + * Note that it is only safe to pass BuilderState as old state when creating new state, when + * - If iterator's next method to get next affected file is never called + * - Iteration of single changed file and its dependencies (iteration through all of its affected files) is complete + */ + export interface BuilderState { /** - * Called when sourceFile is added to the program + * The map of file infos, where there is entry for each file in the program + * The entry is signature of the file (from last emit) or empty string */ - onAddSourceFile(program: Program, sourceFile: SourceFile): void; + fileInfos: ReadonlyMap>; + /** - * Called when sourceFile is removed from the program + * Returns true if module gerneration is not ModuleKind.None */ - onRemoveSourceFile(path: Path): void; + isModuleEmit: boolean; + /** - * For all source files, either "onUpdateSourceFile" or "onUpdateSourceFileWithSameVersion" will be called. - * If the builder is sure that the source file needs an update, "onUpdateSourceFile" will be called; - * otherwise "onUpdateSourceFileWithSameVersion" will be called. + * Map of file referenced or undefined if it wasnt module emit + * The entry is present only if file references other files + * The key is path of file and value is referenced map for that file (for every file referenced, there is entry in the set) */ - onUpdateSourceFile(program: Program, sourceFile: SourceFile): void; + referencedMap: ReadonlyMap | undefined; + /** - * For all source files, either "onUpdateSourceFile" or "onUpdateSourceFileWithSameVersion" will be called. - * If the builder is sure that the source file needs an update, "onUpdateSourceFile" will be called; - * otherwise "onUpdateSourceFileWithSameVersion" will be called. - * This function should return whether the source file should be marked as changed (meaning that something associated with file has changed, e.g. module resolution) + * Set of source file's paths that have been changed, either in resolution or versions */ - onUpdateSourceFileWithSameVersion(program: Program, sourceFile: SourceFile): boolean; + changedFilesSet: ReadonlyMap; + /** - * Gets the files affected by the script info which has updated shape from the known one + * Set of cached semantic diagnostics per file */ - getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile): ReadonlyArray; + semanticDiagnosticsPerFile: ReadonlyMap>; + + /** + * Returns true if this state is safe to use as oldState + */ + canCreateNewStateFrom(): boolean; + + /** + * Gets the files affected by the file path + * This api is only for internal use + */ + /* @internal */ + getFilesAffectedBy(programOfThisState: Program, path: Path): ReadonlyArray; + + /** + * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + */ + emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined; + + /** + * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program + * The semantic diagnostics are cached and managed here + * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files + */ + getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; } - interface FileInfo { + /** + * Information about the source file: Its version and optional signature from last emit + */ + export interface FileInfo { version: string; signature: string; } + export interface AffectedFileEmitResult extends EmitResult { + affectedFile?: SourceFile; + } + + /** + * Referenced files with values for the keys as referenced file's path to be true + */ + export type ReferencedSet = ReadonlyMap; + export interface BuilderOptions { getCanonicalFileName: GetCanonicalFileName; computeHash: (data: string) => string; } - export function createBuilder(options: BuilderOptions): Builder { - let isModuleEmit: boolean | undefined; + export function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly): BuilderState { const fileInfos = createMap(); + const isModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None; + const referencedMap = isModuleEmit ? createMap() : undefined; + const semanticDiagnosticsPerFile = createMap>(); /** The map has key by source file's path that has been changed */ const changedFilesSet = createMap(); const hasShapeChanged = createMap(); let allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; - let emitHandler: EmitHandler; + + // Iterator datas + let affectedFiles: ReadonlyArray | undefined; + let affectedFilesIndex = 0; + const seenAffectedFiles = createMap(); + const getEmitDependentFilesAffectedBy = isModuleEmit ? + getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit; + + const useOldState = oldState && oldState.isModuleEmit === isModuleEmit; + if (useOldState) { + Debug.assert(oldState.canCreateNewStateFrom(), "Cannot use this state as old state"); + Debug.assert(!forEachEntry(oldState.changedFilesSet, (_value, path) => oldState.semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files"); + + copyEntries(oldState.changedFilesSet, changedFilesSet); + copyEntries(oldState.semanticDiagnosticsPerFile, semanticDiagnosticsPerFile); + } + + for (const sourceFile of newProgram.getSourceFiles()) { + const version = sourceFile.version; + let oldInfo: Readonly; + let oldReferences: ReferencedSet; + const newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile); + + // Register changed file + // if not using old state so every file is changed + if (!useOldState || + // File wasnt present earlier + !(oldInfo = oldState.fileInfos.get(sourceFile.path)) || + // versions dont match + oldInfo.version !== version || + // Referenced files changed + !hasSameKeys(newReferences, (oldReferences = oldState.referencedMap && oldState.referencedMap.get(sourceFile.path))) || + // Referenced file was deleted + newReferences && forEachEntry(newReferences, (_value, path) => oldState.fileInfos.has(path) && !newProgram.getSourceFileByPath(path as Path))) { + changedFilesSet.set(sourceFile.path, true); + // All changed files need to re-evaluate its semantic diagnostics + semanticDiagnosticsPerFile.delete(sourceFile.path); + } + + newReferences && referencedMap.set(sourceFile.path, newReferences); + fileInfos.set(sourceFile.path, { version, signature: oldInfo && oldInfo.signature }); + } + + // For removed files, remove the semantic diagnostics removed files as changed + useOldState && oldState.fileInfos.forEach((_value, path) => !fileInfos.has(path) && semanticDiagnosticsPerFile.delete(path)); + + // Set the old state and program to undefined to ensure we arent keeping them alive hence forward + oldState = undefined; + newProgram = undefined; + return { - updateProgram, + fileInfos, + isModuleEmit, + referencedMap, + changedFilesSet, + semanticDiagnosticsPerFile, + canCreateNewStateFrom, getFilesAffectedBy, - emitChangedFiles, - getSemanticDiagnostics, - clear + emitNextAffectedFile, + getSemanticDiagnostics }; - function createProgramGraph(program: Program) { - const currentIsModuleEmit = program.getCompilerOptions().module !== ModuleKind.None; - if (isModuleEmit !== currentIsModuleEmit) { - isModuleEmit = currentIsModuleEmit; - emitHandler = isModuleEmit ? getModuleEmitHandler() : getNonModuleEmitHandler(); - fileInfos.clear(); - semanticDiagnosticsPerFile.clear(); - } - hasShapeChanged.clear(); - allFilesExcludingDefaultLibraryFile = undefined; - mutateMap( - fileInfos, - arrayToMap(program.getSourceFiles(), sourceFile => sourceFile.path), - { - // Add new file info - createNewValue: (_path, sourceFile) => addNewFileInfo(program, sourceFile), - // Remove existing file info - onDeleteValue: removeExistingFileInfo, - // We will update in place instead of deleting existing value and adding new one - onExistingValue: (existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile) - } - ); + /** + * Can use this state as old State if we have iterated through all affected files present + */ + function canCreateNewStateFrom() { + return !affectedFiles || affectedFiles.length <= affectedFilesIndex; } - function registerChangedFile(path: Path) { - changedFilesSet.set(path, true); - // All changed files need to re-evaluate its semantic diagnostics - semanticDiagnosticsPerFile.delete(path); - } - - function addNewFileInfo(program: Program, sourceFile: SourceFile): FileInfo { - registerChangedFile(sourceFile.path); - emitHandler.onAddSourceFile(program, sourceFile); - return { version: sourceFile.version, signature: undefined }; - } - - function removeExistingFileInfo(_existingFileInfo: FileInfo, path: Path) { - // Since we dont need to track removed file as changed file - // We can just remove its diagnostics - changedFilesSet.delete(path); - semanticDiagnosticsPerFile.delete(path); - emitHandler.onRemoveSourceFile(path); - } - - function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile) { - if (existingInfo.version !== sourceFile.version) { - registerChangedFile(sourceFile.path); - existingInfo.version = sourceFile.version; - emitHandler.onUpdateSourceFile(program, sourceFile); - } - else if (emitHandler.onUpdateSourceFileWithSameVersion(program, sourceFile)) { - registerChangedFile(sourceFile.path); - } - } - - function ensureProgramGraph(program: Program) { - if (!emitHandler) { - createProgramGraph(program); - } - } - - function updateProgram(newProgram: Program) { - if (emitHandler) { - createProgramGraph(newProgram); - } - } - - function getFilesAffectedBy(program: Program, path: Path): ReadonlyArray { - ensureProgramGraph(program); - - const sourceFile = program.getSourceFileByPath(path); + /** + * Gets the files affected by the path from the program + */ + function getFilesAffectedBy(programOfThisState: Program, path: Path): ReadonlyArray { + const sourceFile = programOfThisState.getSourceFileByPath(path); if (!sourceFile) { return emptyArray; } - if (!updateShapeSignature(program, sourceFile)) { + if (!updateShapeSignature(programOfThisState, sourceFile)) { return [sourceFile]; } - return emitHandler.getFilesAffectedByUpdatedShape(program, sourceFile); + + return getEmitDependentFilesAffectedBy(programOfThisState, sourceFile); } - function emitChangedFiles(program: Program, writeFileCallback: WriteFileCallback): ReadonlyArray { - ensureProgramGraph(program); - const compilerOptions = program.getCompilerOptions(); + /** + * Emits the next affected file, and returns the EmitResult along with source files emitted + * Returns undefined when iteration is complete + */ + function emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined { + if (affectedFiles) { + while (affectedFilesIndex < affectedFiles.length) { + const affectedFile = affectedFiles[affectedFilesIndex]; + affectedFilesIndex++; + if (!seenAffectedFiles.has(affectedFile.path)) { + seenAffectedFiles.set(affectedFile.path, true); - if (!changedFilesSet.size) { - return emptyArray; + // Emit the affected file + const result = programOfThisState.emit(affectedFile, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers) as AffectedFileEmitResult; + result.affectedFile = affectedFile; + return result; + } + } + + affectedFiles = undefined; } + // Get next changed file + const nextKey = changedFilesSet.keys().next(); + if (nextKey.done) { + // Done + return undefined; + } + + const compilerOptions = programOfThisState.getCompilerOptions(); // With --out or --outFile all outputs go into single file, do it only once if (compilerOptions.outFile || compilerOptions.out) { Debug.assert(semanticDiagnosticsPerFile.size === 0); changedFilesSet.clear(); - return [program.emit(/*targetSourceFile*/ undefined, writeFileCallback)]; + return programOfThisState.emit(/*targetSourceFile*/ undefined, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers); } - const seenFiles = createMap(); - let result: EmitResult[] | undefined; - changedFilesSet.forEach((_true, path) => { - // Get the affected Files by this program - const affectedFiles = getFilesAffectedBy(program, path as Path); - affectedFiles.forEach(affectedFile => { - // Affected files shouldnt have cached diagnostics - semanticDiagnosticsPerFile.delete(affectedFile.path); + // Get next batch of affected files + changedFilesSet.delete(nextKey.value); + affectedFilesIndex = 0; + affectedFiles = getFilesAffectedBy(programOfThisState, nextKey.value as Path); - if (!seenFiles.has(affectedFile.path)) { - seenFiles.set(affectedFile.path, true); + // Clear the semantic diagnostic of affected files + affectedFiles.forEach(affectedFile => semanticDiagnosticsPerFile.delete(affectedFile.path)); - // Emit the affected file - (result || (result = [])).push(program.emit(affectedFile, writeFileCallback)); - } - }); - }); - changedFilesSet.clear(); - return result || emptyArray; + return emitNextAffectedFile(programOfThisState, writeFileCallback, cancellationToken, customTransformers); } - function getSemanticDiagnostics(program: Program, cancellationToken?: CancellationToken): ReadonlyArray { - ensureProgramGraph(program); - Debug.assert(changedFilesSet.size === 0); - - const compilerOptions = program.getCompilerOptions(); + /** + * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program + * The semantic diagnostics are cached and managed here + * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files + */ + function getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { + const compilerOptions = programOfThisState.getCompilerOptions(); if (compilerOptions.outFile || compilerOptions.out) { Debug.assert(semanticDiagnosticsPerFile.size === 0); // We dont need to cache the diagnostics just return them from program - return program.getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken); + return programOfThisState.getSemanticDiagnostics(sourceFile, cancellationToken); + } + + if (sourceFile) { + return getSemanticDiagnosticsOfFile(programOfThisState, sourceFile, cancellationToken); } let diagnostics: Diagnostic[]; - for (const sourceFile of program.getSourceFiles()) { - diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(program, sourceFile, cancellationToken)); + for (const sourceFile of programOfThisState.getSourceFiles()) { + diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(programOfThisState, sourceFile, cancellationToken)); } return diagnostics || emptyArray; } + /** + * Gets the semantic diagnostics either from cache if present, or otherwise from program and caches it + * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files/changed file set + */ function getSemanticDiagnosticsOfFile(program: Program, sourceFile: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { const path = sourceFile.path; const cachedDiagnostics = semanticDiagnosticsPerFile.get(path); @@ -247,15 +302,6 @@ namespace ts { return diagnostics; } - function clear() { - isModuleEmit = undefined; - emitHandler = undefined; - fileInfos.clear(); - semanticDiagnosticsPerFile.clear(); - changedFilesSet.clear(); - hasShapeChanged.clear(); - } - /** * For script files that contains only ambient external modules, although they are not actually external module files, * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, @@ -271,9 +317,10 @@ namespace ts { return true; } - /** - * @return {boolean} indicates if the shape signature has changed since last update. - */ + /** + * Returns if the shape of the signature has changed since last emit + * Note that it also updates the current signature as the latest signature for the file + */ function updateShapeSignature(program: Program, sourceFile: SourceFile) { Debug.assert(!!sourceFile); @@ -360,6 +407,15 @@ namespace ts { } } + /** + * Gets the files referenced by the the file path + */ + function getReferencedByPaths(referencedFilePath: Path) { + return mapDefinedIter(referencedMap.entries(), ([filePath, referencesInFile]) => + referencesInFile.has(referencedFilePath) ? filePath as Path : undefined + ); + } + /** * Gets all files of the program excluding the default library file */ @@ -386,126 +442,53 @@ namespace ts { } } - function getNonModuleEmitHandler(): EmitHandler { - return { - onAddSourceFile: noop, - onRemoveSourceFile: noop, - onUpdateSourceFile: noop, - onUpdateSourceFileWithSameVersion: returnFalse, - getFilesAffectedByUpdatedShape - }; - - function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile): ReadonlyArray { - const options = program.getCompilerOptions(); - // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, - // so returning the file itself is good enough. - if (options && (options.out || options.outFile)) { - return [sourceFile]; - } - return getAllFilesExcludingDefaultLibraryFile(program, sourceFile); + /** + * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { + const compilerOptions = programOfThisState.getCompilerOptions(); + // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, + // so returning the file itself is good enough. + if (compilerOptions && (compilerOptions.out || compilerOptions.outFile)) { + return [sourceFileWithUpdatedShape]; } + return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); } - function getModuleEmitHandler(): EmitHandler { - const references = createMap>(); - return { - onAddSourceFile: setReferences, - onRemoveSourceFile, - onUpdateSourceFile: updateReferences, - onUpdateSourceFileWithSameVersion: updateReferencesTrackingChangedReferences, - getFilesAffectedByUpdatedShape - }; - - function setReferences(program: Program, sourceFile: SourceFile) { - const newReferences = getReferencedFiles(program, sourceFile); - if (newReferences) { - references.set(sourceFile.path, newReferences); - } + /** + * When program emits modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { + if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) { + return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); } - function updateReferences(program: Program, sourceFile: SourceFile) { - const newReferences = getReferencedFiles(program, sourceFile); - if (newReferences) { - references.set(sourceFile.path, newReferences); - } - else { - references.delete(sourceFile.path); - } + const compilerOptions = programOfThisState.getCompilerOptions(); + if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) { + return [sourceFileWithUpdatedShape]; } - function updateReferencesTrackingChangedReferences(program: Program, sourceFile: SourceFile) { - const newReferences = getReferencedFiles(program, sourceFile); - if (!newReferences) { - // Changed if we had references - return references.delete(sourceFile.path); - } + // Now we need to if each file in the referencedBy list has a shape change as well. + // Because if so, its own referencedBy files need to be saved as well to make the + // emitting result consistent with files on disk. + const seenFileNamesMap = createMap(); - const oldReferences = references.get(sourceFile.path); - references.set(sourceFile.path, newReferences); - if (!oldReferences || oldReferences.size !== newReferences.size) { - return true; - } - - // If there are any new references that werent present previously there is change - return forEachEntry(newReferences, (_true, referencedPath) => !oldReferences.delete(referencedPath)) || - // Otherwise its changed if there are more references previously than now - !!oldReferences.size; - } - - function onRemoveSourceFile(removedFilePath: Path) { - // Remove existing references - references.forEach((referencesInFile, filePath) => { - if (referencesInFile.has(removedFilePath)) { - // add files referencing the removedFilePath, as changed files too - const referencedByInfo = fileInfos.get(filePath); - if (referencedByInfo) { - registerChangedFile(filePath as Path); - } - } - }); - // Delete the entry for the removed file path - references.delete(removedFilePath); - } - - function getReferencedByPaths(referencedFilePath: Path) { - return mapDefinedIter(references.entries(), ([filePath, referencesInFile]) => - referencesInFile.has(referencedFilePath) ? filePath as Path : undefined - ); - } - - function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile): ReadonlyArray { - if (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile)) { - return getAllFilesExcludingDefaultLibraryFile(program, sourceFile); - } - - const compilerOptions = program.getCompilerOptions(); - if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) { - return [sourceFile]; - } - - // Now we need to if each file in the referencedBy list has a shape change as well. - // Because if so, its own referencedBy files need to be saved as well to make the - // emitting result consistent with files on disk. - const seenFileNamesMap = createMap(); - - // Start with the paths this file was referenced by - const path = sourceFile.path; - seenFileNamesMap.set(path, sourceFile); - const queue = getReferencedByPaths(path); - while (queue.length > 0) { - const currentPath = queue.pop(); - if (!seenFileNamesMap.has(currentPath)) { - const currentSourceFile = program.getSourceFileByPath(currentPath); - seenFileNamesMap.set(currentPath, currentSourceFile); - if (currentSourceFile && updateShapeSignature(program, currentSourceFile)) { - queue.push(...getReferencedByPaths(currentPath)); - } + // Start with the paths this file was referenced by + seenFileNamesMap.set(sourceFileWithUpdatedShape.path, sourceFileWithUpdatedShape); + const queue = getReferencedByPaths(sourceFileWithUpdatedShape.path); + while (queue.length > 0) { + const currentPath = queue.pop(); + if (!seenFileNamesMap.has(currentPath)) { + const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath); + seenFileNamesMap.set(currentPath, currentSourceFile); + if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile)) { + queue.push(...getReferencedByPaths(currentPath)); } } - - // Return array of values that needs emit - return flatMapIter(seenFileNamesMap.values(), value => value); } + + // Return array of values that needs emit + return flatMapIter(seenFileNamesMap.values(), value => value); } } } diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 01fb45e4f7d..ab1e5cc566e 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -155,8 +155,8 @@ namespace ts { const watchingHost = ts.createWatchingSystemHost(/*pretty*/ undefined, sys, parseConfigFile, reportDiagnostic, reportWatchDiagnostic); watchingHost.beforeCompile = enableStatistics; const afterCompile = watchingHost.afterCompile; - watchingHost.afterCompile = (host, program, builder) => { - afterCompile(host, program, builder); + watchingHost.afterCompile = (host, program) => { + afterCompile(host, program); reportStatistics(program); }; return watchingHost; diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 8e49bb71a15..96605f7b6b9 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -2,7 +2,6 @@ /// /// -/* @internal */ namespace ts { export type DiagnosticReporter = (diagnostic: Diagnostic) => void; export type ParseConfigFile = (configFileName: string, optionsToExtend: CompilerOptions, system: DirectoryStructureHost, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter) => ParsedCommandLine; @@ -19,7 +18,7 @@ namespace ts { // Callbacks to do custom action before creating program and after creating program beforeCompile(compilerOptions: CompilerOptions): void; - afterCompile(host: DirectoryStructureHost, program: Program, builder: Builder): void; + afterCompile(host: DirectoryStructureHost, program: Program): void; } const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = sys ? { @@ -133,6 +132,11 @@ namespace ts { reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system, pretty ? reportDiagnosticWithColorAndContext : reportDiagnosticSimply); reportWatchDiagnostic = reportWatchDiagnostic || createWatchDiagnosticReporter(system); parseConfigFile = parseConfigFile || ts.parseConfigFile; + let builderState: Readonly | undefined; + const options: BuilderOptions = { + getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), + computeHash: data => system.createHash ? system.createHash(data) : data + }; return { system, parseConfigFile, @@ -142,7 +146,9 @@ namespace ts { afterCompile: compileWatchedProgram, }; - function compileWatchedProgram(host: DirectoryStructureHost, program: Program, builder: Builder) { + function compileWatchedProgram(host: DirectoryStructureHost, program: Program) { + builderState = createBuilderState(program, options, builderState); + // First get and report any syntactic errors. const diagnostics = program.getSyntacticDiagnostics().slice(); let reportSemanticDiagnostics = false; @@ -163,22 +169,15 @@ namespace ts { let sourceMaps: SourceMapData[]; let emitSkipped: boolean; - const result = builder.emitChangedFiles(program, writeFile); - if (result.length === 0) { - emitSkipped = true; - } - else { - for (const emitOutput of result) { - if (emitOutput.emitSkipped) { - emitSkipped = true; - } - addRange(diagnostics, emitOutput.diagnostics); - sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps); - } + let affectedEmitResult: AffectedFileEmitResult; + while (affectedEmitResult = builderState.emitNextAffectedFile(program, writeFile)) { + emitSkipped = emitSkipped || affectedEmitResult.emitSkipped; + addRange(diagnostics, affectedEmitResult.diagnostics); + sourceMaps = addRange(sourceMaps, affectedEmitResult.sourceMaps); } if (reportSemanticDiagnostics) { - addRange(diagnostics, builder.getSemanticDiagnostics(program)); + addRange(diagnostics, builderState.getSemanticDiagnostics(program)); } return handleEmitOutputAndReportErrors(host, program, emittedFiles, emitSkipped, diagnostics, reportDiagnostic); @@ -299,8 +298,6 @@ namespace ts { getDirectoryPath(getNormalizedAbsolutePath(configFileName, getCurrentDirectory())) : getCurrentDirectory() ); - // There is no extra check needed since we can just rely on the program to decide emit - const builder = createBuilder({ getCanonicalFileName, computeHash }); synchronizeProgram(); @@ -334,7 +331,6 @@ namespace ts { compilerHost.hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames; program = createProgram(rootFileNames, compilerOptions, compilerHost, program); resolutionCache.finishCachingPerDirectoryResolution(); - builder.updateProgram(program); // Update watches updateMissingFilePathsWatch(program, missingFilesMap || (missingFilesMap = createMap()), watchMissingFilePath); @@ -356,7 +352,7 @@ namespace ts { missingFilePathsRequestedForRelease = undefined; } - afterCompile(directoryStructureHost, program, builder); + afterCompile(directoryStructureHost, program); reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); } @@ -640,9 +636,5 @@ namespace ts { flags ); } - - function computeHash(data: string) { - return system.createHash ? system.createHash(data) : data; - } } } diff --git a/src/harness/unittests/builder.ts b/src/harness/unittests/builder.ts index bfdeb1a4067..60297d2b7fc 100644 --- a/src/harness/unittests/builder.ts +++ b/src/harness/unittests/builder.ts @@ -44,15 +44,16 @@ namespace ts { }); function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray) => void { - const builder = createBuilder({ + let builderState: BuilderState; + const builderOptions: BuilderOptions = { getCanonicalFileName: identity, computeHash: identity - }); + }; return fileNames => { const program = getProgram(); - builder.updateProgram(program); + builderState = createBuilderState(program, builderOptions, builderState); const outputFileNames: string[] = []; - builder.emitChangedFiles(program, fileName => outputFileNames.push(fileName)); + while (builderState.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName))) { } assert.deepEqual(outputFileNames, fileNames); }; } diff --git a/src/server/project.ts b/src/server/project.ts index ce750c78e23..056fedf19af 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -139,7 +139,7 @@ namespace ts.server { /*@internal*/ resolutionCache: ResolutionCache; - private builder: Builder; + private builderState: BuilderState; /** * Set of files names that were updated since the last call to getChangesSinceVersion. */ @@ -442,15 +442,6 @@ namespace ts.server { return this.languageService; } - private ensureBuilder() { - if (!this.builder) { - this.builder = createBuilder({ - getCanonicalFileName: this.projectService.toCanonicalFileName, - computeHash: data => this.projectService.host.createHash(data) - }); - } - } - private shouldEmitFile(scriptInfo: ScriptInfo) { return scriptInfo && !scriptInfo.isDynamicOrHasMixedContent(); } @@ -460,8 +451,11 @@ namespace ts.server { return []; } this.updateGraph(); - this.ensureBuilder(); - return mapDefined(this.builder.getFilesAffectedBy(this.program, scriptInfo.path), + this.builderState = createBuilderState(this.program, { + getCanonicalFileName: this.projectService.toCanonicalFileName, + computeHash: data => this.projectService.host.createHash(data) + }, this.builderState); + return mapDefined(this.builderState.getFilesAffectedBy(this.program, scriptInfo.path), sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined); } @@ -497,6 +491,7 @@ namespace ts.server { } this.languageService.cleanupSemanticCache(); this.languageServiceEnabled = false; + this.builderState = undefined; this.resolutionCache.closeTypeRootsWatch(); this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ false); } @@ -537,7 +532,7 @@ namespace ts.server { this.rootFilesMap = undefined; this.externalFiles = undefined; this.program = undefined; - this.builder = undefined; + this.builderState = undefined; this.resolutionCache.clear(); this.resolutionCache = undefined; this.cachedUnresolvedImportsPerFile = undefined; @@ -787,15 +782,9 @@ namespace ts.server { if (this.setTypings(cachedTypings)) { hasChanges = this.updateGraphWorker() || hasChanges; } - if (this.builder) { - this.builder.updateProgram(this.program); - } } else { this.lastCachedUnresolvedImportsList = undefined; - if (this.builder) { - this.builder.clear(); - } } if (hasChanges) { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 231d7bae3f2..3ec16254780 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3771,6 +3771,70 @@ declare namespace ts { writeByteOrderMark: boolean; text: string; } + /** + * State on which you can query affected files (files to save) and get semantic diagnostics(with their cache managed in the object) + * Note that it is only safe to pass BuilderState as old state when creating new state, when + * - If iterator's next method to get next affected file is never called + * - Iteration of single changed file and its dependencies (iteration through all of its affected files) is complete + */ + interface BuilderState { + /** + * The map of file infos, where there is entry for each file in the program + * The entry is signature of the file (from last emit) or empty string + */ + fileInfos: ReadonlyMap>; + /** + * Returns true if module gerneration is not ModuleKind.None + */ + isModuleEmit: boolean; + /** + * Map of file referenced or undefined if it wasnt module emit + * The entry is present only if file references other files + * The key is path of file and value is referenced map for that file (for every file referenced, there is entry in the set) + */ + referencedMap: ReadonlyMap | undefined; + /** + * Set of source file's paths that have been changed, either in resolution or versions + */ + changedFilesSet: ReadonlyMap; + /** + * Set of cached semantic diagnostics per file + */ + semanticDiagnosticsPerFile: ReadonlyMap>; + /** + * Returns true if this state is safe to use as oldState + */ + canCreateNewStateFrom(): boolean; + /** + * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + */ + emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined; + /** + * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program + * The semantic diagnostics are cached and managed here + * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files + */ + getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + } + /** + * Information about the source file: Its version and optional signature from last emit + */ + interface FileInfo { + version: string; + signature: string; + } + interface AffectedFileEmitResult extends EmitResult { + affectedFile?: SourceFile; + } + /** + * Referenced files with values for the keys as referenced file's path to be true + */ + type ReferencedSet = ReadonlyMap; + interface BuilderOptions { + getCanonicalFileName: (fileName: string) => string; + computeHash: (data: string) => string; + } + function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly): BuilderState; } declare namespace ts { function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; @@ -7210,7 +7274,7 @@ declare namespace ts.server { languageServiceEnabled: boolean; readonly trace?: (s: string) => void; readonly realpath?: (path: string) => string; - private builder; + private builderState; /** * Set of files names that were updated since the last call to getChangesSinceVersion. */ @@ -7271,7 +7335,6 @@ declare namespace ts.server { getGlobalProjectErrors(): ReadonlyArray; getAllProjectErrors(): ReadonlyArray; getLanguageService(ensureSynchronized?: boolean): LanguageService; - private ensureBuilder(); private shouldEmitFile(scriptInfo); getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[]; /** diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 6b2db71b140..5dec286a8b7 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3718,6 +3718,70 @@ declare namespace ts { writeByteOrderMark: boolean; text: string; } + /** + * State on which you can query affected files (files to save) and get semantic diagnostics(with their cache managed in the object) + * Note that it is only safe to pass BuilderState as old state when creating new state, when + * - If iterator's next method to get next affected file is never called + * - Iteration of single changed file and its dependencies (iteration through all of its affected files) is complete + */ + interface BuilderState { + /** + * The map of file infos, where there is entry for each file in the program + * The entry is signature of the file (from last emit) or empty string + */ + fileInfos: ReadonlyMap>; + /** + * Returns true if module gerneration is not ModuleKind.None + */ + isModuleEmit: boolean; + /** + * Map of file referenced or undefined if it wasnt module emit + * The entry is present only if file references other files + * The key is path of file and value is referenced map for that file (for every file referenced, there is entry in the set) + */ + referencedMap: ReadonlyMap | undefined; + /** + * Set of source file's paths that have been changed, either in resolution or versions + */ + changedFilesSet: ReadonlyMap; + /** + * Set of cached semantic diagnostics per file + */ + semanticDiagnosticsPerFile: ReadonlyMap>; + /** + * Returns true if this state is safe to use as oldState + */ + canCreateNewStateFrom(): boolean; + /** + * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + */ + emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined; + /** + * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program + * The semantic diagnostics are cached and managed here + * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files + */ + getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + } + /** + * Information about the source file: Its version and optional signature from last emit + */ + interface FileInfo { + version: string; + signature: string; + } + interface AffectedFileEmitResult extends EmitResult { + affectedFile?: SourceFile; + } + /** + * Referenced files with values for the keys as referenced file's path to be true + */ + type ReferencedSet = ReadonlyMap; + interface BuilderOptions { + getCanonicalFileName: (fileName: string) => string; + computeHash: (data: string) => string; + } + function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly): BuilderState; } declare namespace ts { function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; From 576fe1e995973b96c98d7152af5d27e34dec2d0e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 26 Oct 2017 10:00:23 -0700 Subject: [PATCH 007/172] Expose the watch and builder API in the typescript.d.ts --- src/compiler/tsc.ts | 62 +++- src/compiler/watch.ts | 326 +++++++++++------- .../unittests/reuseProgramStructure.ts | 32 +- src/harness/unittests/tscWatchMode.ts | 123 +++---- src/harness/virtualFileSystemWithWatch.ts | 2 +- src/services/tsconfig.json | 6 +- tests/baselines/reference/api/typescript.d.ts | 66 ++++ 7 files changed, 382 insertions(+), 235 deletions(-) diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index ab1e5cc566e..5342319e75d 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -21,10 +21,10 @@ namespace ts { return diagnostic.messageText; } - let reportDiagnostic = createDiagnosticReporter(sys, reportDiagnosticSimply); + let reportDiagnostic = createDiagnosticReporter(); function udpateReportDiagnostic(options: CompilerOptions) { if (options.pretty) { - reportDiagnostic = createDiagnosticReporter(sys, reportDiagnosticWithColorAndContext); + reportDiagnostic = createDiagnosticReporter(sys, /*pretty*/ true); } } @@ -55,7 +55,7 @@ namespace ts { // If there are any errors due to command line parsing and/or // setting up localization, report them and quit. if (commandLine.errors.length > 0) { - reportDiagnostics(commandLine.errors, reportDiagnostic); + commandLine.errors.forEach(reportDiagnostic); return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); } @@ -110,12 +110,11 @@ namespace ts { const commandLineOptions = commandLine.options; if (configFileName) { - const reportWatchDiagnostic = createWatchDiagnosticReporter(); - const configParseResult = parseConfigFile(configFileName, commandLineOptions, sys, reportDiagnostic, reportWatchDiagnostic); + const configParseResult = parseConfigFile(configFileName, commandLineOptions, sys, reportDiagnostic); udpateReportDiagnostic(configParseResult.options); if (isWatchSet(configParseResult.options)) { reportWatchModeWithoutSysSupport(); - createWatchModeWithConfigFile(configParseResult, commandLineOptions, createWatchingSystemHost(reportWatchDiagnostic)); + createWatchOfConfigFile(configParseResult, commandLineOptions); } else { performCompilation(configParseResult.fileNames, configParseResult.options); @@ -125,7 +124,7 @@ namespace ts { udpateReportDiagnostic(commandLineOptions); if (isWatchSet(commandLineOptions)) { reportWatchModeWithoutSysSupport(); - createWatchModeWithoutConfigFile(commandLine.fileNames, commandLineOptions, createWatchingSystemHost()); + createWatchOfFilesAndCompilerOptions(commandLine.fileNames, commandLineOptions); } else { performCompilation(commandLine.fileNames, commandLineOptions); @@ -151,15 +150,37 @@ namespace ts { return sys.exit(exitStatus); } - function createWatchingSystemHost(reportWatchDiagnostic?: DiagnosticReporter) { - const watchingHost = ts.createWatchingSystemHost(/*pretty*/ undefined, sys, parseConfigFile, reportDiagnostic, reportWatchDiagnostic); - watchingHost.beforeCompile = enableStatistics; - const afterCompile = watchingHost.afterCompile; - watchingHost.afterCompile = (host, program) => { - afterCompile(host, program); + function createProgramCompilerWithBuilderState() { + const compilerWithBuilderState = ts.createProgramCompilerWithBuilderState(sys, reportDiagnostic); + return (host: DirectoryStructureHost, program: Program) => { + compilerWithBuilderState(host, program); reportStatistics(program); }; - return watchingHost; + } + + function createWatchOfConfigFile(configParseResult: ParsedCommandLine, optionsToExtend: CompilerOptions) { + createWatch({ + system: sys, + beforeProgramCreate: enableStatistics, + afterProgramCreate: createProgramCompilerWithBuilderState(), + onConfigFileDiagnostic: reportDiagnostic, + rootFiles: configParseResult.fileNames, + options: configParseResult.options, + configFileName: configParseResult.options.configFilePath, + optionsToExtend, + configFileSpecs: configParseResult.configFileSpecs, + configFileWildCardDirectories: configParseResult.wildcardDirectories + }); + } + + function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions) { + createWatch({ + system: sys, + beforeProgramCreate: enableStatistics, + afterProgramCreate: createProgramCompilerWithBuilderState(), + rootFiles, + options + }); } function compileProgram(program: Program): ExitStatus { @@ -182,7 +203,18 @@ namespace ts { const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit(); addRange(diagnostics, emitDiagnostics); - return handleEmitOutputAndReportErrors(sys, program, emittedFiles, emitSkipped, diagnostics, reportDiagnostic); + sortAndDeduplicateDiagnostics(diagnostics).forEach(reportDiagnostic); + writeFileAndEmittedFileList(sys, program, emittedFiles); + if (emitSkipped && diagnostics.length > 0) { + // If the emitter didn't emit anything, then pass that value along. + return ExitStatus.DiagnosticsPresent_OutputsSkipped; + } + else if (diagnostics.length > 0) { + // The emitter emitted something, inform the caller if that happened in the presence + // of diagnostics or not. + return ExitStatus.DiagnosticsPresent_OutputsGenerated; + } + return ExitStatus.Success; } function enableStatistics(compilerOptions: CompilerOptions) { diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 96605f7b6b9..0ff0d2f6122 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -4,149 +4,97 @@ namespace ts { export type DiagnosticReporter = (diagnostic: Diagnostic) => void; - export type ParseConfigFile = (configFileName: string, optionsToExtend: CompilerOptions, system: DirectoryStructureHost, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter) => ParsedCommandLine; - export interface WatchingSystemHost { - // FS system to use - system: System; - // parse config file - parseConfigFile: ParseConfigFile; - - // Reporting errors - reportDiagnostic: DiagnosticReporter; - reportWatchDiagnostic: DiagnosticReporter; - - // Callbacks to do custom action before creating program and after creating program - beforeCompile(compilerOptions: CompilerOptions): void; - afterCompile(host: DirectoryStructureHost, program: Program): void; - } - - const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = sys ? { + const sysFormatDiagnosticsHost: FormatDiagnosticsHost = sys ? { getCurrentDirectory: () => sys.getCurrentDirectory(), getNewLine: () => sys.newLine, getCanonicalFileName: createGetCanonicalFileName(sys.useCaseSensitiveFileNames) } : undefined; - export function createDiagnosticReporter(system = sys, worker = reportDiagnosticSimply, formatDiagnosticsHost?: FormatDiagnosticsHost): DiagnosticReporter { - return diagnostic => worker(diagnostic, getFormatDiagnosticsHost(), system); - - function getFormatDiagnosticsHost() { - return formatDiagnosticsHost || (formatDiagnosticsHost = system === sys ? defaultFormatDiagnosticsHost : { - getCurrentDirectory: () => system.getCurrentDirectory(), - getNewLine: () => system.newLine, - getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), - }); + /** + * Create a function that reports error by writing to the system and handles the formating of the diagnostic + */ + /*@internal*/ + export function createDiagnosticReporter(system = sys, pretty?: boolean): DiagnosticReporter { + const host: FormatDiagnosticsHost = system === sys ? sysFormatDiagnosticsHost : { + getCurrentDirectory: () => system.getCurrentDirectory(), + getNewLine: () => system.newLine, + getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), + }; + if (!pretty) { + return diagnostic => system.write(ts.formatDiagnostic(diagnostic, host)); } - } - export function createWatchDiagnosticReporter(system = sys): DiagnosticReporter { + const diagnostics: Diagnostic[] = new Array(1); return diagnostic => { - let output = new Date().toLocaleTimeString() + " - "; - output += `${flattenDiagnosticMessageText(diagnostic.messageText, system.newLine)}${system.newLine + system.newLine + system.newLine}`; - system.write(output); + diagnostics[0] = diagnostic; + system.write(formatDiagnosticsWithColorAndContext(diagnostics, host) + host.getNewLine()); + diagnostics[0] = undefined; }; } - export function reportDiagnostics(diagnostics: Diagnostic[], reportDiagnostic: DiagnosticReporter): void { - for (const diagnostic of diagnostics) { - reportDiagnostic(diagnostic); - } - } - - export function reportDiagnosticSimply(diagnostic: Diagnostic, host: FormatDiagnosticsHost, system: System): void { - system.write(ts.formatDiagnostic(diagnostic, host)); - } - - export function reportDiagnosticWithColorAndContext(diagnostic: Diagnostic, host: FormatDiagnosticsHost, system: System): void { - system.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + host.getNewLine()); - } - - export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: DirectoryStructureHost, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter): ParsedCommandLine { + /** + * Reads the config file, reports errors if any and exits if the config file cannot be found + */ + /*@internal*/ + export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: DirectoryStructureHost, reportDiagnostic: DiagnosticReporter): ParsedCommandLine { let configFileText: string; try { configFileText = system.readFile(configFileName); } catch (e) { const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message); - reportWatchDiagnostic(error); + reportDiagnostic(error); system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); return; } if (!configFileText) { const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); - reportDiagnostics([error], reportDiagnostic); + reportDiagnostic(error); system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); return; } const result = parseJsonText(configFileName, configFileText); - reportDiagnostics(result.parseDiagnostics, reportDiagnostic); + result.parseDiagnostics.forEach(reportDiagnostic); const cwd = system.getCurrentDirectory(); const configParseResult = parseJsonSourceFileConfigFileContent(result, system, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd)); - reportDiagnostics(configParseResult.errors, reportDiagnostic); + configParseResult.errors.forEach(reportDiagnostic); return configParseResult; } - function reportEmittedFiles(files: string[], system: DirectoryStructureHost): void { - if (!files || files.length === 0) { - return; - } + /** + * Writes emitted files, source files depending on options + */ + /*@internal*/ + export function writeFileAndEmittedFileList(system: System, program: Program, emittedFiles: string[]) { const currentDir = system.getCurrentDirectory(); - for (const file of files) { + forEach(emittedFiles, file => { const filepath = getNormalizedAbsolutePath(file, currentDir); system.write(`TSFILE: ${filepath}${system.newLine}`); - } - } - - export function handleEmitOutputAndReportErrors(system: DirectoryStructureHost, program: Program, - emittedFiles: string[], emitSkipped: boolean, - diagnostics: Diagnostic[], reportDiagnostic: DiagnosticReporter - ): ExitStatus { - reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), reportDiagnostic); - reportEmittedFiles(emittedFiles, system); + }); if (program.getCompilerOptions().listFiles) { forEach(program.getSourceFiles(), file => { system.write(file.fileName + system.newLine); }); } - - if (emitSkipped && diagnostics.length > 0) { - // If the emitter didn't emit anything, then pass that value along. - return ExitStatus.DiagnosticsPresent_OutputsSkipped; - } - else if (diagnostics.length > 0) { - // The emitter emitted something, inform the caller if that happened in the presence - // of diagnostics or not. - return ExitStatus.DiagnosticsPresent_OutputsGenerated; - } - return ExitStatus.Success; } - export function createWatchingSystemHost(pretty?: DiagnosticStyle, system = sys, - parseConfigFile?: ParseConfigFile, reportDiagnostic?: DiagnosticReporter, - reportWatchDiagnostic?: DiagnosticReporter - ): WatchingSystemHost { - reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system, pretty ? reportDiagnosticWithColorAndContext : reportDiagnosticSimply); - reportWatchDiagnostic = reportWatchDiagnostic || createWatchDiagnosticReporter(system); - parseConfigFile = parseConfigFile || ts.parseConfigFile; + /** + * Creates the function that compiles the program by maintaining the builder state and also return diagnostic reporter + */ + export function createProgramCompilerWithBuilderState(system = sys, reportDiagnostic?: DiagnosticReporter) { + reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); let builderState: Readonly | undefined; const options: BuilderOptions = { getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), computeHash: data => system.createHash ? system.createHash(data) : data }; - return { - system, - parseConfigFile, - reportDiagnostic, - reportWatchDiagnostic, - beforeCompile: noop, - afterCompile: compileWatchedProgram, - }; - function compileWatchedProgram(host: DirectoryStructureHost, program: Program) { + return (host: DirectoryStructureHost, program: Program) => { builderState = createBuilderState(program, options, builderState); // First get and report any syntactic errors. @@ -179,8 +127,9 @@ namespace ts { if (reportSemanticDiagnostics) { addRange(diagnostics, builderState.getSemanticDiagnostics(program)); } - return handleEmitOutputAndReportErrors(host, program, emittedFiles, emitSkipped, - diagnostics, reportDiagnostic); + + sortAndDeduplicateDiagnostics(diagnostics).forEach(reportDiagnostic); + writeFileAndEmittedFileList(system, program, emittedFiles); function ensureDirectoriesExist(directoryPath: string) { if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists(directoryPath)) { @@ -210,24 +159,120 @@ namespace ts { } } } + }; + } + + export interface WatchHost { + /** FS system to use */ + system: System; + + /** Custom action before creating the program */ + beforeProgramCreate(compilerOptions: CompilerOptions): void; + /** Custom action after new program creation is successful */ + afterProgramCreate(host: DirectoryStructureHost, program: Program): void; + } + + /** + * Host to create watch with root files and options + */ + export interface WatchOfFilesAndCompilerOptionsHost extends WatchHost { + /** root files to use to generate program */ + rootFiles: string[]; + + /** Compiler options */ + options: CompilerOptions; + } + + /** + * Host to create watch with config file + */ + export interface WatchOfConfigFileHost extends WatchHost { + /** Name of the config file to compile */ + configFileName: string; + + /** Options to extend */ + optionsToExtend?: CompilerOptions; + + // Reports errors in the config file + onConfigFileDiagnostic(diagnostic: Diagnostic): void; + } + + /*@internal*/ + /** + * Host to create watch with config file that is already parsed (from tsc) + */ + export interface WatchOfConfigFileHost extends WatchHost { + rootFiles?: string[]; + options?: CompilerOptions; + optionsToExtend?: CompilerOptions; + configFileSpecs?: ConfigFileSpecs; + configFileWildCardDirectories?: MapLike; + } + + export interface Watch { + /** Synchronize the program with the changes */ + synchronizeProgram(): void; + /** Get current program */ + /*@internal*/ + getProgram(): Program; + } + + /** + * Creates the watch what generates program using the config file + */ + export interface WatchOfConfigFile extends Watch { + } + + /** + * Creates the watch that generates program using the root files and compiler options + */ + export interface WatchOfFilesAndCompilerOptions extends Watch { + /** Updates the root files in the program, only if this is not config file compilation */ + updateRootFileNames(fileNames: string[]): void; + } + + /** + * Create the watched program for config file + */ + export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { + return createWatch({ + system, + beforeProgramCreate: noop, + afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic), + onConfigFileDiagnostic: reportDiagnostic || createDiagnosticReporter(system), + configFileName, + optionsToExtend + }); + } + + /** + * Create the watched program for root files and compiler options + */ + export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfFilesAndCompilerOptions { + return createWatch({ + system, + beforeProgramCreate: noop, + afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic), + rootFiles, + options + }); + } + + /** + * Creates the watch from the host for root files and compiler options + */ + export function createWatch(host: WatchOfFilesAndCompilerOptionsHost): WatchOfFilesAndCompilerOptions; + /** + * Creates the watch from the host for config file + */ + export function createWatch(host: WatchOfConfigFileHost): WatchOfConfigFile; + export function createWatch(host: WatchOfFilesAndCompilerOptionsHost | WatchOfConfigFileHost): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { + interface HostFileInfo { + version: number; + sourceFile: SourceFile; + fileWatcher: FileWatcher; } - } - export function createWatchModeWithConfigFile(configParseResult: ParsedCommandLine, optionsToExtend: CompilerOptions = {}, watchingHost?: WatchingSystemHost) { - return createWatchMode(configParseResult.fileNames, configParseResult.options, watchingHost, configParseResult.options.configFilePath, configParseResult.configFileSpecs, configParseResult.wildcardDirectories, optionsToExtend); - } - - export function createWatchModeWithoutConfigFile(rootFileNames: string[], compilerOptions: CompilerOptions, watchingHost?: WatchingSystemHost) { - return createWatchMode(rootFileNames, compilerOptions, watchingHost); - } - - interface HostFileInfo { - version: number; - sourceFile: SourceFile; - fileWatcher: FileWatcher; - } - - function createWatchMode(rootFileNames: string[], compilerOptions: CompilerOptions, watchingHost?: WatchingSystemHost, configFileName?: string, configFileSpecs?: ConfigFileSpecs, configFileWildCardDirectories?: MapLike, optionsToExtendForConfigFile?: CompilerOptions) { let program: Program; let reloadLevel: ConfigFileProgramReloadLevel; // level to indicate if the program needs to be reloaded from config file/just filenames etc let missingFilesMap: Map; // Map of file watchers for the missing files @@ -239,16 +284,21 @@ namespace ts { let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed + const { system, configFileName, onConfigFileDiagnostic, afterProgramCreate, beforeProgramCreate, optionsToExtend: optionsToExtendForConfigFile = {} } = host as WatchOfConfigFileHost; + let { rootFiles: rootFileNames, options: compilerOptions, configFileSpecs, configFileWildCardDirectories } = host as WatchOfConfigFileHost; + + // From tsc we want to get already parsed result and hence check for rootFileNames + const directoryStructureHost = configFileName ? createCachedDirectoryStructureHost(system) : system; + if (configFileName && !rootFileNames) { + parseConfigFile(); + } + const loggingEnabled = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; const writeLog: (s: string) => void = loggingEnabled ? s => { system.write(s); system.write(system.newLine); } : noop; const watchFile = compilerOptions.extendedDiagnostics ? ts.addFileWatcherWithLogging : loggingEnabled ? ts.addFileWatcherWithOnlyTriggerLogging : ts.addFileWatcher; const watchFilePath = compilerOptions.extendedDiagnostics ? ts.addFilePathWatcherWithLogging : ts.addFilePathWatcher; const watchDirectoryWorker = compilerOptions.extendedDiagnostics ? ts.addDirectoryWatcherWithLogging : ts.addDirectoryWatcher; - watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty); - const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost; - - const directoryStructureHost = configFileName ? createCachedDirectoryStructureHost(system) : system; if (configFileName) { watchFile(system, configFileName, scheduleProgramReload, writeLog); } @@ -304,7 +354,9 @@ namespace ts { // Update the wild card directory watch watchConfigFileWildCardDirectories(); - return () => program; + return configFileName ? + { getProgram: () => program, synchronizeProgram } : + { getProgram: () => program, synchronizeProgram, updateRootFileNames }; function synchronizeProgram() { writeLog(`Synchronizing program`); @@ -321,7 +373,7 @@ namespace ts { return; } - beforeCompile(compilerOptions); + beforeProgramCreate(compilerOptions); // Compile the program const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !program; @@ -352,10 +404,16 @@ namespace ts { missingFilePathsRequestedForRelease = undefined; } - afterCompile(directoryStructureHost, program); + afterProgramCreate(directoryStructureHost, program); reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); } + function updateRootFileNames(files: string[]) { + Debug.assert(!configFileName, "Cannot update root file names with config file watch mode"); + rootFileNames = files; + scheduleProgramUpdate(); + } + function toPath(fileName: string) { return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName); } @@ -468,6 +526,10 @@ namespace ts { } } + function reportWatchDiagnostic(diagnostic: Diagnostic) { + system.write(`${new Date().toLocaleTimeString()} - ${flattenDiagnosticMessageText(diagnostic.messageText, newLine)}${newLine + newLine + newLine}`); + } + // Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch // operations (such as saving all modified files in an editor) a chance to complete before we kick // off a new compilation. @@ -505,7 +567,7 @@ namespace ts { function reloadFileNamesFromConfigFile() { const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, directoryStructureHost); if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) { - reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); + onConfigFileDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); } rootFileNames = result.fileNames; @@ -519,19 +581,22 @@ namespace ts { const cachedHost = directoryStructureHost as CachedDirectoryStructureHost; cachedHost.clearCache(); - const configParseResult = parseConfigFile(configFileName, optionsToExtendForConfigFile, cachedHost, reportDiagnostic, reportWatchDiagnostic); - rootFileNames = configParseResult.fileNames; - compilerOptions = configParseResult.options; + parseConfigFile(); hasChangedCompilerOptions = true; - configFileSpecs = configParseResult.configFileSpecs; - configFileWildCardDirectories = configParseResult.wildcardDirectories; - synchronizeProgram(); // Update the wild card directory watch watchConfigFileWildCardDirectories(); } + function parseConfigFile() { + const configParseResult = ts.parseConfigFile(configFileName, optionsToExtendForConfigFile, directoryStructureHost as CachedDirectoryStructureHost, onConfigFileDiagnostic); + rootFileNames = configParseResult.fileNames; + compilerOptions = configParseResult.options; + configFileSpecs = configParseResult.configFileSpecs; + configFileWildCardDirectories = configParseResult.wildcardDirectories; + } + function onSourceFileChange(fileName: string, eventKind: FileWatcherEventKind, path: Path) { updateCachedSystemWithFile(fileName, path, eventKind); const hostSourceFile = sourceFilesCache.get(path); @@ -590,11 +655,16 @@ namespace ts { } function watchConfigFileWildCardDirectories() { - updateWatchingWildcardDirectories( - watchedWildcardDirectories || (watchedWildcardDirectories = createMap()), - createMapFromTemplate(configFileWildCardDirectories), - watchWildcardDirectory - ); + if (configFileWildCardDirectories) { + updateWatchingWildcardDirectories( + watchedWildcardDirectories || (watchedWildcardDirectories = createMap()), + createMapFromTemplate(configFileWildCardDirectories), + watchWildcardDirectory + ); + } + else if (watchedWildcardDirectories) { + clearMap(watchedWildcardDirectories, closeFileWatcherOf); + } } function watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags) { diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index fdccc8a7795..cc330300d33 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -871,7 +871,6 @@ namespace ts { }); }); - import TestSystem = ts.TestFSWithWatch.TestServerHost; type FileOrFolder = ts.TestFSWithWatch.FileOrFolder; import createTestSystem = ts.TestFSWithWatch.createWatchedSystem; import libFile = ts.TestFSWithWatch.libFile; @@ -897,30 +896,21 @@ namespace ts { return JSON.parse(JSON.stringify(filesOrOptions)); } - function createWatchingSystemHost(host: TestSystem) { - return ts.createWatchingSystemHost(/*pretty*/ undefined, host); - } - - function verifyProgramWithoutConfigFile(watchingSystemHost: WatchingSystemHost, rootFiles: string[], options: CompilerOptions) { - const program = createWatchModeWithoutConfigFile(rootFiles, options, watchingSystemHost)(); + function verifyProgramWithoutConfigFile(system: System, rootFiles: string[], options: CompilerOptions) { + const program = createWatchOfFilesAndCompilerOptions(rootFiles, options, system).getProgram(); verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options)); } - function getConfigParseResult(watchingSystemHost: WatchingSystemHost, configFileName: string) { - return parseConfigFile(configFileName, {}, watchingSystemHost.system, watchingSystemHost.reportDiagnostic, watchingSystemHost.reportWatchDiagnostic); - } - - function verifyProgramWithConfigFile(watchingSystemHost: WatchingSystemHost, configFile: string) { - const result = getConfigParseResult(watchingSystemHost, configFile); - const program = createWatchModeWithConfigFile(result, {}, watchingSystemHost)(); - const { fileNames, options } = getConfigParseResult(watchingSystemHost, configFile); + function verifyProgramWithConfigFile(system: System, configFileName: string) { + const program = createWatchOfConfigFile(configFileName, {}, system).getProgram(); + const { fileNames, options } = parseConfigFile(configFileName, {}, system, notImplemented); verifyProgramIsUptoDate(program, fileNames, options); } function verifyProgram(files: FileOrFolder[], rootFiles: string[], options: CompilerOptions, configFile: string) { - const watchingSystemHost = createWatchingSystemHost(createTestSystem(files)); - verifyProgramWithoutConfigFile(watchingSystemHost, rootFiles, options); - verifyProgramWithConfigFile(watchingSystemHost, configFile); + const system = createTestSystem(files); + verifyProgramWithoutConfigFile(system, rootFiles, options); + verifyProgramWithConfigFile(system, configFile); } it("has empty options", () => { @@ -1031,11 +1021,9 @@ namespace ts { }; const configFile: FileOrFolder = { path: "/src/tsconfig.json", - content: JSON.stringify({ compilerOptions, include: ["packages/**/ *.ts"] }) + content: JSON.stringify({ compilerOptions, include: ["packages/**/*.ts"] }) }; - - const watchingSystemHost = createWatchingSystemHost(createTestSystem([app, module1, module2, module3, libFile, configFile])); - verifyProgramWithConfigFile(watchingSystemHost, configFile.path); + verifyProgramWithConfigFile(createTestSystem([app, module1, module2, module3, libFile, configFile]), configFile.path); }); }); } diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index 4e2d63cec90..a3647aead4a 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -22,23 +22,14 @@ namespace ts.tscWatch { checkFileNames(`Program rootFileNames`, program.getRootFileNames(), expectedFiles); } - function createWatchingSystemHost(system: WatchedSystem) { - return ts.createWatchingSystemHost(/*pretty*/ undefined, system); + function createWatchOfConfigFile(configFileName: string, host: WatchedSystem) { + const watch = ts.createWatchOfConfigFile(configFileName, {}, host); + return () => watch.getProgram(); } - function parseConfigFile(configFileName: string, watchingSystemHost: WatchingSystemHost) { - return ts.parseConfigFile(configFileName, {}, watchingSystemHost.system, watchingSystemHost.reportDiagnostic, watchingSystemHost.reportWatchDiagnostic); - } - - function createWatchModeWithConfigFile(configFilePath: string, host: WatchedSystem) { - const watchingSystemHost = createWatchingSystemHost(host); - const configFileResult = parseConfigFile(configFilePath, watchingSystemHost); - return ts.createWatchModeWithConfigFile(configFileResult, {}, watchingSystemHost); - } - - function createWatchModeWithoutConfigFile(fileNames: string[], host: WatchedSystem, options: CompilerOptions = {}) { - const watchingSystemHost = createWatchingSystemHost(host); - return ts.createWatchModeWithoutConfigFile(fileNames, options, watchingSystemHost); + function createWatchOfFilesAndCompilerOptions(rootFiles: string[], host: WatchedSystem, options: CompilerOptions = {}) { + const watch = ts.createWatchOfFilesAndCompilerOptions(rootFiles, options, host); + return () => watch.getProgram(); } function getEmittedLineForMultiFileOutput(file: FileOrFolder, host: WatchedSystem) { @@ -190,7 +181,7 @@ namespace ts.tscWatch { content: `export let x: number` }; const host = createWatchedSystem([appFile, moduleFile, libFile]); - const watch = createWatchModeWithoutConfigFile([appFile.path], host); + const watch = createWatchOfFilesAndCompilerOptions([appFile.path], host); checkProgramActualFiles(watch(), [appFile.path, libFile.path, moduleFile.path]); @@ -215,7 +206,7 @@ namespace ts.tscWatch { const host = createWatchedSystem([f1, config], { useCaseSensitiveFileNames: false }); const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path)); - const watch = createWatchModeWithConfigFile(upperCaseConfigFilePath, host); + const watch = createWatchOfConfigFile(upperCaseConfigFilePath, host); checkProgramActualFiles(watch(), [combinePaths(getDirectoryPath(upperCaseConfigFilePath), getBaseFileName(f1.path))]); }); @@ -244,14 +235,10 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([configFile, libFile, file1, file2, file3]); - const watchingSystemHost = createWatchingSystemHost(host); - const configFileResult = parseConfigFile(configFile.path, watchingSystemHost); - assert.equal(configFileResult.errors.length, 0, `expect no errors in config file, got ${JSON.stringify(configFileResult.errors)}`); + const watch = ts.createWatchOfConfigFile(configFile.path, {}, host, notImplemented); - const watch = ts.createWatchModeWithConfigFile(configFileResult, {}, watchingSystemHost); - - checkProgramActualFiles(watch(), [file1.path, libFile.path, file2.path]); - checkProgramRootFiles(watch(), [file1.path, file2.path]); + checkProgramActualFiles(watch.getProgram(), [file1.path, libFile.path, file2.path]); + checkProgramRootFiles(watch.getProgram(), [file1.path, file2.path]); checkWatchedFiles(host, [configFile.path, file1.path, file2.path, libFile.path]); const configDir = getDirectoryPath(configFile.path); checkWatchedDirectories(host, [configDir, combinePaths(configDir, projectSystem.nodeModulesAtTypes)], /*recursive*/ true); @@ -267,7 +254,7 @@ namespace ts.tscWatch { content: `{}` }; const host = createWatchedSystem([commonFile1, libFile, configFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); const configDir = getDirectoryPath(configFile.path); checkWatchedDirectories(host, [configDir, combinePaths(configDir, projectSystem.nodeModulesAtTypes)], /*recursive*/ true); @@ -291,7 +278,7 @@ namespace ts.tscWatch { }` }; const host = createWatchedSystem([commonFile1, commonFile2, configFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); const commonFile3 = "/a/b/commonFile3.ts"; checkProgramRootFiles(watch(), [commonFile1.path, commonFile3]); @@ -304,7 +291,7 @@ namespace ts.tscWatch { content: `{}` }; const host = createWatchedSystem([commonFile1, commonFile2, configFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]); // delete commonFile2 @@ -326,7 +313,7 @@ namespace ts.tscWatch { let x = y` }; const host = createWatchedSystem([file1, libFile]); - const watch = createWatchModeWithoutConfigFile([file1.path], host); + const watch = createWatchOfFilesAndCompilerOptions([file1.path], host); checkProgramRootFiles(watch(), [file1.path]); checkProgramActualFiles(watch(), [file1.path, libFile.path]); @@ -352,7 +339,7 @@ namespace ts.tscWatch { }; const files = [commonFile1, commonFile2, configFile]; const host = createWatchedSystem(files); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]); configFile.content = `{ @@ -379,7 +366,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([commonFile1, commonFile2, excludedFile1, configFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]); }); @@ -407,7 +394,7 @@ namespace ts.tscWatch { }; const files = [file1, nodeModuleFile, classicModuleFile, configFile]; const host = createWatchedSystem(files); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramRootFiles(watch(), [file1.path]); checkProgramActualFiles(watch(), [file1.path, nodeModuleFile.path]); @@ -435,7 +422,7 @@ namespace ts.tscWatch { }` }; const host = createWatchedSystem([commonFile1, commonFile2, libFile, configFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramRootFiles(watch(), [commonFile1.path, commonFile2.path]); }); @@ -453,7 +440,7 @@ namespace ts.tscWatch { content: `export let y = 1;` }; const host = createWatchedSystem([file1, file2, file3]); - const watch = createWatchModeWithoutConfigFile([file1.path], host); + const watch = createWatchOfFilesAndCompilerOptions([file1.path], host); checkProgramRootFiles(watch(), [file1.path]); checkProgramActualFiles(watch(), [file1.path, file2.path]); @@ -482,7 +469,7 @@ namespace ts.tscWatch { content: `export let y = 1;` }; const host = createWatchedSystem([file1, file2, file3]); - const watch = createWatchModeWithoutConfigFile([file1.path], host); + const watch = createWatchOfFilesAndCompilerOptions([file1.path], host); checkProgramActualFiles(watch(), [file1.path, file2.path, file3.path]); host.reloadFS([file1, file3]); @@ -505,7 +492,7 @@ namespace ts.tscWatch { content: `export let y = 1;` }; const host = createWatchedSystem([file1, file2, file3]); - const watch = createWatchModeWithoutConfigFile([file1.path, file3.path], host); + const watch = createWatchOfFilesAndCompilerOptions([file1.path, file3.path], host); checkProgramActualFiles(watch(), [file1.path, file2.path, file3.path]); host.reloadFS([file1, file3]); @@ -533,7 +520,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file1, file2, file3, configFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramRootFiles(watch(), [file2.path, file3.path]); checkProgramActualFiles(watch(), [file1.path, file2.path, file3.path]); @@ -555,10 +542,10 @@ namespace ts.tscWatch { content: "export let y = 1;" }; const host = createWatchedSystem([file1, file2, file3]); - const watch = createWatchModeWithoutConfigFile([file2.path, file3.path], host); + const watch = createWatchOfFilesAndCompilerOptions([file2.path, file3.path], host); checkProgramActualFiles(watch(), [file2.path, file3.path]); - const watch2 = createWatchModeWithoutConfigFile([file1.path], host); + const watch2 = createWatchOfFilesAndCompilerOptions([file1.path], host); checkProgramActualFiles(watch2(), [file1.path, file2.path, file3.path]); // Previous program shouldnt be updated @@ -581,7 +568,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file1, configFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramActualFiles(watch(), [file1.path]); host.reloadFS([file1, file2, configFile]); @@ -606,7 +593,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file1, file2, configFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramActualFiles(watch(), [file1.path]); @@ -636,7 +623,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file1, file2, configFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramActualFiles(watch(), [file1.path, file2.path]); const modifiedConfigFile = { @@ -664,7 +651,7 @@ namespace ts.tscWatch { content: JSON.stringify({ compilerOptions: {} }) }; const host = createWatchedSystem([file1, file2, libFile, config]); - const watch = createWatchModeWithConfigFile(config.path, host); + const watch = createWatchOfConfigFile(config.path, host); checkProgramActualFiles(watch(), [file1.path, file2.path, libFile.path]); checkOutputErrors(host, emptyArray, /*isInitial*/ true); @@ -688,7 +675,7 @@ namespace ts.tscWatch { content: "{" }; const host = createWatchedSystem([file1, corruptedConfig]); - const watch = createWatchModeWithConfigFile(corruptedConfig.path, host); + const watch = createWatchOfConfigFile(corruptedConfig.path, host); checkProgramActualFiles(watch(), [file1.path]); }); @@ -738,7 +725,7 @@ namespace ts.tscWatch { }) }; const host = createWatchedSystem([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" }); - const watch = createWatchModeWithConfigFile(config1.path, host); + const watch = createWatchOfConfigFile(config1.path, host); checkProgramActualFiles(watch(), [libES5.path, app.path]); @@ -763,7 +750,7 @@ namespace ts.tscWatch { }) }; const host = createWatchedSystem([f, config]); - const watch = createWatchModeWithConfigFile(config.path, host); + const watch = createWatchOfConfigFile(config.path, host); checkProgramActualFiles(watch(), [f.path]); }); @@ -777,7 +764,7 @@ namespace ts.tscWatch { content: 'import * as T from "./moduleFile"; T.bar();' }; const host = createWatchedSystem([moduleFile, file1, libFile]); - const watch = createWatchModeWithoutConfigFile([file1.path], host); + const watch = createWatchOfFilesAndCompilerOptions([file1.path], host); checkOutputErrors(host, emptyArray, /*isInitial*/ true); const moduleFileOldPath = moduleFile.path; @@ -809,7 +796,7 @@ namespace ts.tscWatch { content: `{}` }; const host = createWatchedSystem([moduleFile, file1, configFile, libFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkOutputErrors(host, emptyArray, /*isInitial*/ true); const moduleFileOldPath = moduleFile.path; @@ -844,7 +831,7 @@ namespace ts.tscWatch { path: "/a/c" }; const host = createWatchedSystem([f1, config, node, cwd], { currentDirectory: cwd.path }); - const watch = createWatchModeWithConfigFile(config.path, host); + const watch = createWatchOfConfigFile(config.path, host); checkProgramActualFiles(watch(), [f1.path, node.path]); }); @@ -859,7 +846,7 @@ namespace ts.tscWatch { content: 'import * as T from "./moduleFile"; T.bar();' }; const host = createWatchedSystem([file1, libFile]); - const watch = createWatchModeWithoutConfigFile([file1.path], host); + const watch = createWatchOfFilesAndCompilerOptions([file1.path], host); checkOutputErrors(host, [ getDiagnosticModuleNotFoundOfFile(watch(), file1, "./moduleFile") @@ -886,7 +873,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file, configFile, libFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkOutputErrors(host, [ getUnknownCompilerOption(watch(), configFile, "foo"), getUnknownCompilerOption(watch(), configFile, "allowJS") @@ -906,7 +893,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file, configFile, libFile]); - createWatchModeWithConfigFile(configFile.path, host); + createWatchOfConfigFile(configFile.path, host); checkOutputErrors(host, emptyArray, /*isInitial*/ true); }); @@ -923,7 +910,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file, configFile, libFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkOutputErrors(host, emptyArray, /*isInitial*/ true); configFile.content = `{ @@ -959,7 +946,7 @@ namespace ts.tscWatch { }; const host = createWatchedSystem([file1, configFile, libFile]); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramActualFiles(watch(), [libFile.path]); }); @@ -985,7 +972,7 @@ namespace ts.tscWatch { content: `export const x: number` }; const host = createWatchedSystem([f, config, t1, t2], { currentDirectory: getDirectoryPath(f.path) }); - const watch = createWatchModeWithConfigFile(config.path, host); + const watch = createWatchOfConfigFile(config.path, host); checkProgramActualFiles(watch(), [t1.path, t2.path]); }); @@ -996,7 +983,7 @@ namespace ts.tscWatch { content: "let x = 1" }; const host = createWatchedSystem([f, libFile]); - const watch = createWatchModeWithoutConfigFile([f.path], host, { allowNonTsExtensions: true }); + const watch = createWatchOfFilesAndCompilerOptions([f.path], host, { allowNonTsExtensions: true }); checkProgramActualFiles(watch(), [f.path, libFile.path]); }); @@ -1024,7 +1011,7 @@ namespace ts.tscWatch { const files = [file, libFile, configFile]; const host = createWatchedSystem(files); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); const errors = () => [ getDiagnosticOfFile(watch().getCompilerOptions().configFile, configFile.content.indexOf('"allowJs"'), '"allowJs"'.length, Diagnostics.Option_0_cannot_be_specified_with_option_1, "allowJs", "declaration"), getDiagnosticOfFile(watch().getCompilerOptions().configFile, configFile.content.indexOf('"declaration"'), '"declaration"'.length, Diagnostics.Option_0_cannot_be_specified_with_option_1, "allowJs", "declaration") @@ -1080,7 +1067,7 @@ namespace ts.tscWatch { const files = [f1, f2, config, libFile]; host.reloadFS(files); - createWatchModeWithConfigFile(config.path, host); + createWatchOfConfigFile(config.path, host); const allEmittedLines = getEmittedLines(files); checkOutputContains(host, allEmittedLines); @@ -1142,7 +1129,7 @@ namespace ts.tscWatch { mapOfFilesWritten.set(p, count ? count + 1 : 1); return originalWriteFile(p, content); }; - createWatchModeWithConfigFile(configFile.path, host); + createWatchOfConfigFile(configFile.path, host); if (useOutFile) { // Only out file assert.equal(mapOfFilesWritten.size, 1); @@ -1226,7 +1213,7 @@ namespace ts.tscWatch { host.reloadFS(firstReloadFileList ? getFiles(firstReloadFileList) : files); // Initial compile - createWatchModeWithConfigFile(configFile.path, host); + createWatchOfConfigFile(configFile.path, host); if (firstCompilationEmitFiles) { checkAffectedLines(host, getFiles(firstCompilationEmitFiles), allEmittedFiles); } @@ -1537,11 +1524,11 @@ namespace ts.tscWatch { // Initial compile if (configFile) { - createWatchModeWithConfigFile(configFile.path, host); + createWatchOfConfigFile(configFile.path, host); } else { // First file as the root - createWatchModeWithoutConfigFile([files[0].path], host, { listEmittedFiles: true }); + createWatchOfFilesAndCompilerOptions([files[0].path], host, { listEmittedFiles: true }); } checkOutputContains(host, allEmittedFiles); @@ -1661,7 +1648,7 @@ namespace ts.tscWatch { const files = [root, imported, libFile]; const host = createWatchedSystem(files); - const watch = createWatchModeWithoutConfigFile([root.path], host, { module: ModuleKind.AMD }); + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); const f1IsNotModule = getDiagnosticOfFileFromProgram(watch(), root.path, root.content.indexOf('"f1"'), '"f1"'.length, Diagnostics.File_0_is_not_a_module, imported.path); const cannotFindFoo = getDiagnosticOfFileFromProgram(watch(), imported.path, imported.content.indexOf("foo"), "foo".length, Diagnostics.Cannot_find_name_0, "foo"); @@ -1762,7 +1749,7 @@ namespace ts.tscWatch { return originalFileExists.call(host, fileName); }; - const watch = createWatchModeWithoutConfigFile([root.path], host, { module: ModuleKind.AMD }); + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); checkOutputErrors(host, [ @@ -1804,7 +1791,7 @@ namespace ts.tscWatch { return originalFileExists.call(host, fileName); }; - const watch = createWatchModeWithoutConfigFile([root.path], host, { module: ModuleKind.AMD }); + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { module: ModuleKind.AMD }); assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called"); checkOutputErrors(host, emptyArray, /*isInitial*/ true); @@ -1853,7 +1840,7 @@ declare module "fs" { const filesWithNodeType = files.concat(packageJson, nodeType); const host = createWatchedSystem(files, { currentDirectory: "/a/b" }); - const watch = createWatchModeWithoutConfigFile([root.path], host, { }); + const watch = createWatchOfFilesAndCompilerOptions([root.path], host, { }); checkOutputErrors(host, [ getDiagnosticModuleNotFoundOfFile(watch(), root, "fs") @@ -1895,7 +1882,7 @@ declare module "fs" { const files = [root, file, libFile]; const host = createWatchedSystem(files, { currentDirectory: "/a/b" }); - const watch = createWatchModeWithoutConfigFile([root.path, file.path], host, {}); + const watch = createWatchOfFilesAndCompilerOptions([root.path, file.path], host, {}); checkOutputErrors(host, [ getDiagnosticModuleNotFoundOfFile(watch(), root, "fs") @@ -1937,7 +1924,7 @@ declare module "fs" { const outDirFolder = "/a/b/projects/myProject/dist/"; const programFiles = [file1, file2, module1, libFile]; const host = createWatchedSystem(programFiles.concat(configFile), { currentDirectory: "/a/b/projects/myProject/" }); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramActualFiles(watch(), programFiles.map(f => f.path)); checkOutputErrors(host, emptyArray, /*isInitial*/ true); const expectedFiles: ExpectedFile[] = [ @@ -2014,7 +2001,7 @@ declare module "fs" { }; const files = [configFile, file1, file2, libFile]; const host = createWatchedSystem(files); - const watch = createWatchModeWithConfigFile(configFile.path, host); + const watch = createWatchOfConfigFile(configFile.path, host); checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path)); file1.content = "var zz30 = 100;"; diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index d4d203cabbb..f2a178610c9 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -460,7 +460,7 @@ interface Array {}` private invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind) { const callbacks = this.watchedFiles.get(this.toPath(fileFullPath)); - invokeWatcherCallbacks(callbacks, ({ cb, fileName }) => cb(fileName, eventKind)); + invokeWatcherCallbacks(callbacks, ({ cb }) => cb(fileFullPath, eventKind)); } private getRelativePathToDirectory(directoryFullPath: string, fileFullPath: string) { diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index d73014a93a2..ba944bafd9d 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -1,4 +1,4 @@ -{ +{ "extends": "../tsconfig-base", "compilerOptions": { "removeComments": false, @@ -37,6 +37,10 @@ "../compiler/declarationEmitter.ts", "../compiler/emitter.ts", "../compiler/program.ts", + "../compiler/builder.ts", + "../compiler/resolutionCache.ts", + "../compiler/watch.ts", + "../compiler/watchUtilities.ts", "../compiler/commandLineParser.ts", "../compiler/diagnosticInformationMap.generated.ts", "types.ts", diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 5dec286a8b7..fb5ee37012d 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3812,6 +3812,72 @@ declare namespace ts { */ function createProgram(rootNames: ReadonlyArray, options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program; } +declare namespace ts { + type DiagnosticReporter = (diagnostic: Diagnostic) => void; + /** + * Creates the function that compiles the program by maintaining the builder state and also return diagnostic reporter + */ + function createProgramCompilerWithBuilderState(system?: System, reportDiagnostic?: DiagnosticReporter): (host: DirectoryStructureHost, program: Program) => void; + interface WatchHost { + /** FS system to use */ + system: System; + /** Custom action before creating the program */ + beforeProgramCreate(compilerOptions: CompilerOptions): void; + /** Custom action after new program creation is successful */ + afterProgramCreate(host: DirectoryStructureHost, program: Program): void; + } + /** + * Host to create watch with root files and options + */ + interface WatchOfFilesAndCompilerOptionsHost extends WatchHost { + /** root files to use to generate program */ + rootFiles: string[]; + /** Compiler options */ + options: CompilerOptions; + } + /** + * Host to create watch with config file + */ + interface WatchOfConfigFileHost extends WatchHost { + /** Name of the config file to compile */ + configFileName: string; + /** Options to extend */ + optionsToExtend?: CompilerOptions; + onConfigFileDiagnostic(diagnostic: Diagnostic): void; + } + interface Watch { + /** Synchronize the program with the changes */ + synchronizeProgram(): void; + } + /** + * Creates the watch what generates program using the config file + */ + interface WatchOfConfigFile extends Watch { + } + /** + * Creates the watch that generates program using the root files and compiler options + */ + interface WatchOfFilesAndCompilerOptions extends Watch { + /** Updates the root files in the program, only if this is not config file compilation */ + updateRootFileNames(fileNames: string[]): void; + } + /** + * Create the watched program for config file + */ + function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile; + /** + * Create the watched program for root files and compiler options + */ + function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfFilesAndCompilerOptions; + /** + * Creates the watch from the host for root files and compiler options + */ + function createWatch(host: WatchOfFilesAndCompilerOptionsHost): WatchOfFilesAndCompilerOptions; + /** + * Creates the watch from the host for config file + */ + function createWatch(host: WatchOfConfigFileHost): WatchOfConfigFile; +} declare namespace ts { function parseCommandLine(commandLine: ReadonlyArray, readFile?: (path: string) => string | undefined): ParsedCommandLine; /** From 7ebf9d9f9d76e4a655d0079a7663a574842fa373 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 7 Nov 2017 11:35:38 -0800 Subject: [PATCH 008/172] Lint errors fix --- src/compiler/builder.ts | 8 ++++++-- src/harness/unittests/builder.ts | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 9a60557ae36..0881f68800c 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -166,12 +166,16 @@ namespace ts { semanticDiagnosticsPerFile.delete(sourceFile.path); } - newReferences && referencedMap.set(sourceFile.path, newReferences); + if (newReferences) { + referencedMap.set(sourceFile.path, newReferences); + } fileInfos.set(sourceFile.path, { version, signature: oldInfo && oldInfo.signature }); } // For removed files, remove the semantic diagnostics removed files as changed - useOldState && oldState.fileInfos.forEach((_value, path) => !fileInfos.has(path) && semanticDiagnosticsPerFile.delete(path)); + if (useOldState) { + oldState.fileInfos.forEach((_value, path) => !fileInfos.has(path) && semanticDiagnosticsPerFile.delete(path)); + } // Set the old state and program to undefined to ensure we arent keeping them alive hence forward oldState = undefined; diff --git a/src/harness/unittests/builder.ts b/src/harness/unittests/builder.ts index 60297d2b7fc..89b0852bd32 100644 --- a/src/harness/unittests/builder.ts +++ b/src/harness/unittests/builder.ts @@ -53,7 +53,9 @@ namespace ts { const program = getProgram(); builderState = createBuilderState(program, builderOptions, builderState); const outputFileNames: string[] = []; - while (builderState.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName))) { } + // tslint:disable-next-line no-empty + while (builderState.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName))) { + } assert.deepEqual(outputFileNames, fileNames); }; } From 3c5a6e1ae7848d97e7178a4d3e8043cf33d15fca Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 7 Nov 2017 13:08:20 -0800 Subject: [PATCH 009/172] Allow watch host to specify module name resolver --- src/compiler/watch.ts | 10 ++++++++-- tests/baselines/reference/api/typescript.d.ts | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 0ff0d2f6122..ff352c0febc 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -170,6 +170,9 @@ namespace ts { beforeProgramCreate(compilerOptions: CompilerOptions): void; /** Custom action after new program creation is successful */ afterProgramCreate(host: DirectoryStructureHost, program: Program): void; + + /** Optional module name resolver */ + moduleNameResolver?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } /** @@ -308,6 +311,9 @@ namespace ts { const getCachedDirectoryStructureHost = configFileName && (() => directoryStructureHost as CachedDirectoryStructureHost); const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); let newLine = getNewLineCharacter(compilerOptions, system); + const resolveModuleNames: (moduleNames: string[], containingFile: string, reusedNames?: string[]) => ResolvedModule[] = host.moduleNameResolver ? + (moduleNames, containingFile, reusedNames) => host.moduleNameResolver(moduleNames, containingFile, reusedNames) : + (moduleNames, containingFile, reusedNames?) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, /*logChanges*/ false); const compilerHost: CompilerHost & ResolutionCacheHost = { // Members for CompilerHost @@ -328,7 +334,7 @@ namespace ts { getDirectories: path => directoryStructureHost.getDirectories(path), realpath, resolveTypeReferenceDirectives: (typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile), - resolveModuleNames: (moduleNames, containingFile, reusedNames?) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, /*logChanges*/ false), + resolveModuleNames, onReleaseOldSourceFile, // Members for ResolutionCacheHost toPath, @@ -341,7 +347,7 @@ namespace ts { hasChangedAutomaticTypeDirectiveNames = true; scheduleProgramUpdate(); }, - writeLog + writeLog, }; // Cache for the module resolution const resolutionCache = createResolutionCache(compilerHost, configFileName ? diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index fb5ee37012d..63e2d453b18 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3825,6 +3825,8 @@ declare namespace ts { beforeProgramCreate(compilerOptions: CompilerOptions): void; /** Custom action after new program creation is successful */ afterProgramCreate(host: DirectoryStructureHost, program: Program): void; + /** Optional module name resolver */ + moduleNameResolver?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } /** * Host to create watch with root files and options From c9a17f325b96f812604b529ce2ba36d21e3441b8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 9 Nov 2017 13:35:56 -0800 Subject: [PATCH 010/172] Add api to get the dependencies of the file --- src/compiler/builder.ts | 54 ++++++++++++++++++- .../reference/api/tsserverlibrary.d.ts | 4 ++ tests/baselines/reference/api/typescript.d.ts | 4 ++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 0881f68800c..efa0f0a0372 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -93,6 +93,11 @@ namespace ts { * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files */ getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + + /** + * Get all the dependencies of the file + */ + getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; } /** @@ -190,7 +195,8 @@ namespace ts { canCreateNewStateFrom, getFilesAffectedBy, emitNextAffectedFile, - getSemanticDiagnostics + getSemanticDiagnostics, + getAllDependencies }; /** @@ -306,6 +312,52 @@ namespace ts { return diagnostics; } + /** + * Get all the dependencies of the sourceFile + */ + function getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[] { + const compilerOptions = programOfThisState.getCompilerOptions(); + // With --out or --outFile all outputs go into single file, all files depend on each other + if (compilerOptions.outFile || compilerOptions.out) { + return programOfThisState.getSourceFiles().map(getFileName); + } + + // If this is non module emit, or its a global file, it depends on all the source files + if (!isModuleEmit || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) { + return programOfThisState.getSourceFiles().map(getFileName); + } + + // Get the references, traversing deep from the referenceMap + Debug.assert(!!referencedMap); + const seenMap = createMap(); + const queue = [sourceFile.path]; + while (queue.length) { + const path = queue.pop(); + if (!seenMap.has(path)) { + seenMap.set(path, true); + const references = referencedMap.get(path); + if (references) { + const iterator = references.keys(); + for (let { value, done } = iterator.next(); !done; { value, done } = iterator.next()) { + queue.push(value as Path); + } + } + } + } + + return flatMapIter(seenMap.keys(), path => { + const file = programOfThisState.getSourceFileByPath(path as Path); + if (file) { + return file.fileName; + } + return path; + }); + } + + function getFileName(sourceFile: SourceFile) { + return sourceFile.fileName; + } + /** * For script files that contains only ambient external modules, although they are not actually external module files, * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 3ec16254780..5c306474533 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3815,6 +3815,10 @@ declare namespace ts { * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files */ getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get all the dependencies of the file + */ + getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; } /** * Information about the source file: Its version and optional signature from last emit diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 63e2d453b18..2212a859c0a 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3762,6 +3762,10 @@ declare namespace ts { * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files */ getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get all the dependencies of the file + */ + getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; } /** * Information about the source file: Its version and optional signature from last emit From 6d36a3d778b188a29cef1271cb24e270d93d644b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 14 Nov 2017 11:35:20 -0800 Subject: [PATCH 011/172] Make the versions in the source file non zero when the source file is created --- src/compiler/watch.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index ff352c0febc..07fb9203b6a 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -468,9 +468,9 @@ namespace ts { else { let fileWatcher: FileWatcher; if (sourceFile) { - sourceFile.version = "0"; + sourceFile.version = "1"; fileWatcher = watchFilePath(system, fileName, onSourceFileChange, path, writeLog); - sourceFilesCache.set(path, { sourceFile, version: 0, fileWatcher }); + sourceFilesCache.set(path, { sourceFile, version: 1, fileWatcher }); } else { sourceFilesCache.set(path, "0"); @@ -612,7 +612,7 @@ namespace ts { resolutionCache.invalidateResolutionOfFile(path); if (!isString(hostSourceFile)) { hostSourceFile.fileWatcher.close(); - sourceFilesCache.set(path, (hostSourceFile.version++).toString()); + sourceFilesCache.set(path, (++hostSourceFile.version).toString()); } } else { From 85ce1d0398e252192b1826b9c2818a091b783f3e Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 14 Nov 2017 14:47:58 -0800 Subject: [PATCH 012/172] Make the builder state as internal and expose builder instead of builder state --- src/compiler/builder.ts | 627 ++++++++++++------ src/compiler/watch.ts | 15 +- src/harness/unittests/builder.ts | 9 +- src/server/project.ts | 19 +- .../reference/api/tsserverlibrary.d.ts | 85 ++- tests/baselines/reference/api/typescript.d.ts | 85 ++- 6 files changed, 521 insertions(+), 319 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index efa0f0a0372..8703d83aa1d 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -1,29 +1,56 @@ /// +/*@internal*/ namespace ts { - export interface EmitOutput { - outputFiles: OutputFile[]; - emitSkipped: boolean; - } - - export interface OutputFile { - name: string; - writeByteOrderMark: boolean; - text: string; - } - - /* @internal */ export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput { const outputFiles: OutputFile[] = []; const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); - return { outputFiles, emitSkipped: emitResult.emitSkipped }; + return { outputFiles, emitSkipped: emitResult.emitSkipped }; function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) { outputFiles.push({ name: fileName, writeByteOrderMark, text }); } } + /** + * Internal Builder to get files affected by another file + */ + export interface InternalBuilder extends BaseBuilder { + /** + * Gets the files affected by the file path + * This api is only for internal use + */ + /*@internal*/ + getFilesAffectedBy(programOfThisState: Program, path: Path): ReadonlyArray; + } + + /** + * Create the internal builder to get files affected by sourceFile + */ + export function createInternalBuilder(options: BuilderOptions): InternalBuilder { + return createBuilder(options, BuilderType.InternalBuilder); + } + + export enum BuilderType { + InternalBuilder, + SemanticDiagnosticsBuilder, + EmitAndSemanticDiagnosticsBuilder + } + + /** + * Information about the source file: Its version and optional signature from last emit + */ + interface FileInfo { + version: string; + signature?: string; + } + + /** + * Referenced files with values for the keys as referenced file's path to be true + */ + type ReferencedSet = ReadonlyMap; + function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined) { if (map1 === undefined) { return map2 === undefined; @@ -35,191 +62,273 @@ namespace ts { return map1.size === map2.size && !forEachEntry(map1, (_value, key) => !map2.has(key)); } - /** - * State on which you can query affected files (files to save) and get semantic diagnostics(with their cache managed in the object) - * Note that it is only safe to pass BuilderState as old state when creating new state, when - * - If iterator's next method to get next affected file is never called - * - Iteration of single changed file and its dependencies (iteration through all of its affected files) is complete - */ - export interface BuilderState { + export function createBuilder(options: BuilderOptions, builderType: BuilderType.InternalBuilder): InternalBuilder; + export function createBuilder(options: BuilderOptions, builderType: BuilderType.SemanticDiagnosticsBuilder): SemanticDiagnosticsBuilder; + export function createBuilder(options: BuilderOptions, builderType: BuilderType.EmitAndSemanticDiagnosticsBuilder): EmitAndSemanticDiagnosticsBuilder; + export function createBuilder(options: BuilderOptions, builderType: BuilderType) { /** - * The map of file infos, where there is entry for each file in the program - * The entry is signature of the file (from last emit) or empty string + * Information of the file eg. its version, signature etc */ - fileInfos: ReadonlyMap>; - - /** - * Returns true if module gerneration is not ModuleKind.None - */ - isModuleEmit: boolean; - - /** - * Map of file referenced or undefined if it wasnt module emit - * The entry is present only if file references other files - * The key is path of file and value is referenced map for that file (for every file referenced, there is entry in the set) - */ - referencedMap: ReadonlyMap | undefined; - - /** - * Set of source file's paths that have been changed, either in resolution or versions - */ - changedFilesSet: ReadonlyMap; - - /** - * Set of cached semantic diagnostics per file - */ - semanticDiagnosticsPerFile: ReadonlyMap>; - - /** - * Returns true if this state is safe to use as oldState - */ - canCreateNewStateFrom(): boolean; - - /** - * Gets the files affected by the file path - * This api is only for internal use - */ - /* @internal */ - getFilesAffectedBy(programOfThisState: Program, path: Path): ReadonlyArray; - - /** - * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete - */ - emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined; - - /** - * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program - * The semantic diagnostics are cached and managed here - * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files - */ - getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; - - /** - * Get all the dependencies of the file - */ - getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; - } - - /** - * Information about the source file: Its version and optional signature from last emit - */ - export interface FileInfo { - version: string; - signature: string; - } - - export interface AffectedFileEmitResult extends EmitResult { - affectedFile?: SourceFile; - } - - /** - * Referenced files with values for the keys as referenced file's path to be true - */ - export type ReferencedSet = ReadonlyMap; - - export interface BuilderOptions { - getCanonicalFileName: GetCanonicalFileName; - computeHash: (data: string) => string; - } - - export function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly): BuilderState { const fileInfos = createMap(); - const isModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None; - const referencedMap = isModuleEmit ? createMap() : undefined; + /** + * true if module emit is enabled + */ + let isModuleEmit: boolean; + + /** + * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled + * Otherwise undefined + */ + let referencedMap: Map | undefined; + + /** + * Get the files affected by the source file. + * This is dependent on whether its a module emit or not and hence function expression + */ + let getEmitDependentFilesAffectedBy: (programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map | undefined) => ReadonlyArray; + + /** + * Cache of semantic diagnostics for files with their Path being the key + */ const semanticDiagnosticsPerFile = createMap>(); - /** The map has key by source file's path that has been changed */ + + /** + * The map has key by source file's path that has been changed + */ const changedFilesSet = createMap(); - const hasShapeChanged = createMap(); + + /** + * Map of files that have already called update signature. + * That means hence forth these files are assumed to have + * no change in their signature for this version of the program + */ + const hasCalledUpdateShapeSignature = createMap(); + + /** + * Cache of all files excluding default library file for the current program + */ let allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; - // Iterator datas + /** + * Set of affected files being iterated + */ let affectedFiles: ReadonlyArray | undefined; + /** + * Current index to retrieve affected file from + */ let affectedFilesIndex = 0; + /** + * Current changed file for iterating over affected files + */ + let currentChangedFilePath: Path | undefined; + /** + * Map of file signatures, with key being file path, calculated while getting current changed file's affected files + * These will be commited whenever the iteration through affected files of current changed file is complete + */ + const currentAffectedFilesSignatures = createMap(); + /** + * Already seen affected files + */ const seenAffectedFiles = createMap(); - const getEmitDependentFilesAffectedBy = isModuleEmit ? - getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit; - const useOldState = oldState && oldState.isModuleEmit === isModuleEmit; - if (useOldState) { - Debug.assert(oldState.canCreateNewStateFrom(), "Cannot use this state as old state"); - Debug.assert(!forEachEntry(oldState.changedFilesSet, (_value, path) => oldState.semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files"); - - copyEntries(oldState.changedFilesSet, changedFilesSet); - copyEntries(oldState.semanticDiagnosticsPerFile, semanticDiagnosticsPerFile); + switch (builderType) { + case BuilderType.InternalBuilder: + return getInternalBuilder(); + case BuilderType.SemanticDiagnosticsBuilder: + return getSemanticDiagnosticsBuilder(); + case BuilderType.EmitAndSemanticDiagnosticsBuilder: + return getEmitAndSemanticDiagnosticsBuilder(); + default: + notImplemented(); } - for (const sourceFile of newProgram.getSourceFiles()) { - const version = sourceFile.version; - let oldInfo: Readonly; - let oldReferences: ReferencedSet; - const newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile); - - // Register changed file - // if not using old state so every file is changed - if (!useOldState || - // File wasnt present earlier - !(oldInfo = oldState.fileInfos.get(sourceFile.path)) || - // versions dont match - oldInfo.version !== version || - // Referenced files changed - !hasSameKeys(newReferences, (oldReferences = oldState.referencedMap && oldState.referencedMap.get(sourceFile.path))) || - // Referenced file was deleted - newReferences && forEachEntry(newReferences, (_value, path) => oldState.fileInfos.has(path) && !newProgram.getSourceFileByPath(path as Path))) { - changedFilesSet.set(sourceFile.path, true); - // All changed files need to re-evaluate its semantic diagnostics - semanticDiagnosticsPerFile.delete(sourceFile.path); - } - - if (newReferences) { - referencedMap.set(sourceFile.path, newReferences); - } - fileInfos.set(sourceFile.path, { version, signature: oldInfo && oldInfo.signature }); + function getInternalBuilder(): InternalBuilder { + return { + updateProgram, + getFilesAffectedBy, + getAllDependencies + }; } - // For removed files, remove the semantic diagnostics removed files as changed - if (useOldState) { - oldState.fileInfos.forEach((_value, path) => !fileInfos.has(path) && semanticDiagnosticsPerFile.delete(path)); + function getSemanticDiagnosticsBuilder(): SemanticDiagnosticsBuilder { + return { + updateProgram, + getAllDependencies, + getSemanticDiagnosticsOfNextAffectedFile, + getSemanticDiagnostics + }; } - // Set the old state and program to undefined to ensure we arent keeping them alive hence forward - oldState = undefined; - newProgram = undefined; - - return { - fileInfos, - isModuleEmit, - referencedMap, - changedFilesSet, - semanticDiagnosticsPerFile, - canCreateNewStateFrom, - getFilesAffectedBy, - emitNextAffectedFile, - getSemanticDiagnostics, - getAllDependencies - }; + function getEmitAndSemanticDiagnosticsBuilder(): EmitAndSemanticDiagnosticsBuilder { + return { + updateProgram, + getAllDependencies, + emitNextAffectedFile, + getSemanticDiagnostics + }; + } /** - * Can use this state as old State if we have iterated through all affected files present + * Update current state to reflect new program + * Updates changed files, references, file infos etc */ - function canCreateNewStateFrom() { - return !affectedFiles || affectedFiles.length <= affectedFilesIndex; + function updateProgram(newProgram: Program) { + const newProgramHasModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None; + const oldReferencedMap = referencedMap; + if (isModuleEmit !== newProgramHasModuleEmit) { + // Changes in the module emit, clear out everything and initialize as if first time + + // Clear file information and semantic diagnostics + fileInfos.clear(); + semanticDiagnosticsPerFile.clear(); + + // Clear changed files and affected files information + changedFilesSet.clear(); + affectedFiles = undefined; + currentChangedFilePath = undefined; + currentAffectedFilesSignatures.clear(); + + // Update the reference map creation + referencedMap = newProgramHasModuleEmit ? createMap() : undefined; + + // Update the module emit + isModuleEmit = newProgramHasModuleEmit; + getEmitDependentFilesAffectedBy = isModuleEmit ? + getFilesAffectedByUpdatedShapeWhenModuleEmit : + getFilesAffectedByUpdatedShapeWhenNonModuleEmit; + } + else { + if (currentChangedFilePath) { + // Remove the diagnostics for all the affected files since we should resume the state such that + // the whole iteration on currentChangedFile never happened + affectedFiles.map(sourceFile => semanticDiagnosticsPerFile.delete(sourceFile.path)); + affectedFiles = undefined; + currentAffectedFilesSignatures.clear(); + } + else { + // Verify the sanity of old state + Debug.assert(!affectedFiles && !currentAffectedFilesSignatures.size, "Cannot reuse if only few affected files of currentChangedFile were iterated"); + } + Debug.assert(!forEachEntry(changedFilesSet, (_value, path) => semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files"); + } + + // Clear datas that cant be retained beyond previous state + seenAffectedFiles.clear(); + hasCalledUpdateShapeSignature.clear(); + allFilesExcludingDefaultLibraryFile = undefined; + + // Create the reference map and update changed files + for (const sourceFile of newProgram.getSourceFiles()) { + const version = sourceFile.version; + const newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile); + const oldInfo = fileInfos.get(sourceFile.path); + let oldReferences: ReferencedSet; + + // Register changed file if its new file or we arent reusing old state + if (!oldInfo) { + // New file: Set the file info + fileInfos.set(sourceFile.path, { version }); + changedFilesSet.set(sourceFile.path, true); + } + // versions dont match + else if (oldInfo.version !== version || + // Referenced files changed + !hasSameKeys(newReferences, (oldReferences = oldReferencedMap && oldReferencedMap.get(sourceFile.path))) || + // Referenced file was deleted in the new program + newReferences && forEachEntry(newReferences, (_value, path) => !newProgram.getSourceFileByPath(path as Path) && fileInfos.has(path))) { + + // Changed file: Update the version, set as changed file + oldInfo.version = version; + changedFilesSet.set(sourceFile.path, true); + + // All changed files need to re-evaluate its semantic diagnostics + semanticDiagnosticsPerFile.delete(sourceFile.path); + } + + // Set the references + if (newReferences) { + referencedMap.set(sourceFile.path, newReferences); + } + else if (referencedMap) { + referencedMap.delete(sourceFile.path); + } + } + + // For removed files, remove the semantic diagnostics and file info + if (fileInfos.size > newProgram.getSourceFiles().length) { + fileInfos.forEach((_value, path) => { + if (!newProgram.getSourceFileByPath(path as Path)) { + fileInfos.delete(path); + semanticDiagnosticsPerFile.delete(path); + if (referencedMap) { + referencedMap.delete(path); + } + } + }); + } } /** * Gets the files affected by the path from the program */ - function getFilesAffectedBy(programOfThisState: Program, path: Path): ReadonlyArray { + function getFilesAffectedBy(programOfThisState: Program, path: Path, cacheToUpdateSignature?: Map): ReadonlyArray { const sourceFile = programOfThisState.getSourceFileByPath(path); if (!sourceFile) { return emptyArray; } - if (!updateShapeSignature(programOfThisState, sourceFile)) { + if (!updateShapeSignature(programOfThisState, sourceFile, cacheToUpdateSignature)) { return [sourceFile]; } - return getEmitDependentFilesAffectedBy(programOfThisState, sourceFile); + return getEmitDependentFilesAffectedBy(programOfThisState, sourceFile, cacheToUpdateSignature); + } + + function getNextAffectedFile(programOfThisState: Program): SourceFile | Program | undefined { + while (true) { + if (affectedFiles) { + while (affectedFilesIndex < affectedFiles.length) { + const affectedFile = affectedFiles[affectedFilesIndex]; + affectedFilesIndex++; + if (!seenAffectedFiles.has(affectedFile.path)) { + // Set the next affected file as seen and remove the cached semantic diagnostics + seenAffectedFiles.set(affectedFile.path, true); + semanticDiagnosticsPerFile.delete(affectedFile.path); + return affectedFile; + } + } + + // Remove the changed file from the change set + changedFilesSet.delete(currentChangedFilePath); + currentChangedFilePath = undefined; + // Commit the changes in file signature + currentAffectedFilesSignatures.forEach((signature, path) => fileInfos.get(path).signature = signature); + currentAffectedFilesSignatures.clear(); + affectedFiles = undefined; + } + + // Get next changed file + const nextKey = changedFilesSet.keys().next(); + if (nextKey.done) { + // Done + return undefined; + } + + const compilerOptions = programOfThisState.getCompilerOptions(); + // With --out or --outFile all outputs go into single file + // so operations are performed directly on program, return program + if (compilerOptions.outFile || compilerOptions.out) { + Debug.assert(semanticDiagnosticsPerFile.size === 0); + changedFilesSet.clear(); + return programOfThisState; + } + + // Get next batch of affected files + currentChangedFilePath = nextKey.value as Path; + affectedFilesIndex = 0; + affectedFiles = getFilesAffectedBy(programOfThisState, nextKey.value as Path, currentAffectedFilesSignatures); + } } /** @@ -227,47 +336,48 @@ namespace ts { * Returns undefined when iteration is complete */ function emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined { - if (affectedFiles) { - while (affectedFilesIndex < affectedFiles.length) { - const affectedFile = affectedFiles[affectedFilesIndex]; - affectedFilesIndex++; - if (!seenAffectedFiles.has(affectedFile.path)) { - seenAffectedFiles.set(affectedFile.path, true); - - // Emit the affected file - const result = programOfThisState.emit(affectedFile, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers) as AffectedFileEmitResult; - result.affectedFile = affectedFile; - return result; - } - } - - affectedFiles = undefined; - } - - // Get next changed file - const nextKey = changedFilesSet.keys().next(); - if (nextKey.done) { + const affectedFile = getNextAffectedFile(programOfThisState); + if (!affectedFile) { // Done return undefined; } - - const compilerOptions = programOfThisState.getCompilerOptions(); - // With --out or --outFile all outputs go into single file, do it only once - if (compilerOptions.outFile || compilerOptions.out) { - Debug.assert(semanticDiagnosticsPerFile.size === 0); - changedFilesSet.clear(); + else if (affectedFile === programOfThisState) { + // When whole program is affected, do emit only once (eg when --out or --outFile is specified) return programOfThisState.emit(/*targetSourceFile*/ undefined, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers); } - // Get next batch of affected files - changedFilesSet.delete(nextKey.value); - affectedFilesIndex = 0; - affectedFiles = getFilesAffectedBy(programOfThisState, nextKey.value as Path); + // Emit the affected file + const targetSourceFile = affectedFile as SourceFile; + const result = programOfThisState.emit(targetSourceFile, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers) as AffectedFileEmitResult; + result.affectedFile = targetSourceFile; + return result; + } - // Clear the semantic diagnostic of affected files - affectedFiles.forEach(affectedFile => semanticDiagnosticsPerFile.delete(affectedFile.path)); + /** + * Return the semantic diagnostics for the next affected file or undefined if iteration is complete + * If provided ignoreSourceFile would be called before getting the diagnostics and would ignore the sourceFile if the returned value was true + */ + function getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray { + while (true) { + const affectedFile = getNextAffectedFile(programOfThisState); + if (!affectedFile) { + // Done + return undefined; + } + else if (affectedFile === programOfThisState) { + // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified) + return programOfThisState.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken); + } - return emitNextAffectedFile(programOfThisState, writeFileCallback, cancellationToken, customTransformers); + // Get diagnostics for the affected file if its not ignored + const targetSourceFile = affectedFile as SourceFile; + if (ignoreSourceFile && ignoreSourceFile(targetSourceFile)) { + // Get next affected file + continue; + } + + return getSemanticDiagnosticsOfFile(programOfThisState, targetSourceFile, cancellationToken); + } } /** @@ -276,6 +386,7 @@ namespace ts { * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files */ function getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { + Debug.assert(!affectedFiles || affectedFiles[affectedFilesIndex - 1] !== sourceFile || !semanticDiagnosticsPerFile.has(sourceFile.path)); const compilerOptions = programOfThisState.getCompilerOptions(); if (compilerOptions.outFile || compilerOptions.out) { Debug.assert(semanticDiagnosticsPerFile.size === 0); @@ -373,19 +484,20 @@ namespace ts { return true; } - /** - * Returns if the shape of the signature has changed since last emit - * Note that it also updates the current signature as the latest signature for the file - */ - function updateShapeSignature(program: Program, sourceFile: SourceFile) { + /** + * Returns if the shape of the signature has changed since last emit + * Note that it also updates the current signature as the latest signature for the file + */ + function updateShapeSignature(program: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map | undefined) { Debug.assert(!!sourceFile); // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate - if (hasShapeChanged.has(sourceFile.path)) { + if (hasCalledUpdateShapeSignature.has(sourceFile.path)) { return false; } - hasShapeChanged.set(sourceFile.path, true); + Debug.assert(!cacheToUpdateSignature || !cacheToUpdateSignature.has(sourceFile.path)); + hasCalledUpdateShapeSignature.set(sourceFile.path, true); const info = fileInfos.get(sourceFile.path); Debug.assert(!!info); @@ -393,13 +505,13 @@ namespace ts { let latestSignature: string; if (sourceFile.isDeclarationFile) { latestSignature = sourceFile.version; - info.signature = latestSignature; + setLatestSigature(); } else { const emitOutput = getFileEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true); if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { latestSignature = options.computeHash(emitOutput.outputFiles[0].text); - info.signature = latestSignature; + setLatestSigature(); } else { latestSignature = prevSignature; @@ -407,6 +519,15 @@ namespace ts { } return !prevSignature || latestSignature !== prevSignature; + + function setLatestSigature() { + if (cacheToUpdateSignature) { + cacheToUpdateSignature.set(sourceFile.path, latestSignature); + } + else { + info.signature = latestSignature; + } + } } /** @@ -514,7 +635,7 @@ namespace ts { /** * When program emits modular code, gets the files affected by the sourceFile whose shape has changed */ - function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { + function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map | undefined) { if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) { return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); } @@ -537,7 +658,7 @@ namespace ts { if (!seenFileNamesMap.has(currentPath)) { const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath); seenFileNamesMap.set(currentPath, currentSourceFile); - if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile)) { + if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile, cacheToUpdateSignature)) { queue.push(...getReferencedByPaths(currentPath)); } } @@ -548,3 +669,93 @@ namespace ts { } } } + +namespace ts { + export interface EmitOutput { + outputFiles: OutputFile[]; + emitSkipped: boolean; + } + + export interface OutputFile { + name: string; + writeByteOrderMark: boolean; + text: string; + } + + export interface AffectedFileEmitResult extends EmitResult { + affectedFile?: SourceFile; + } + + export interface BuilderOptions { + getCanonicalFileName: (fileName: string) => string; + computeHash: (data: string) => string; + } + + /** + * Builder to manage the program state changes + */ + export interface BaseBuilder { + /** + * Updates the program in the builder to represent new state + */ + updateProgram(newProgram: Program): void; + + /** + * Get all the dependencies of the file + */ + getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; + } + + /** + * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files + */ + export interface SemanticDiagnosticsBuilder extends BaseBuilder { + /** + * Gets the semantic diagnostics from the program for the next affected file and caches it + * Returns undefined if the iteration is complete + */ + getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray; + + /** + * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program + * The semantic diagnostics are cached and managed here + * Note that it is assumed that the when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics + */ + getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + } + + /** + * The builder that can handle the changes in program and iterate through changed file to emit the files + * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files + */ + export interface EmitAndSemanticDiagnosticsBuilder extends BaseBuilder { + /** + * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + */ + emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined; + + /** + * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program + * The semantic diagnostics are cached and managed here + * Note that it is assumed that the when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics + */ + getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + } + + /** + * Create the builder to manage semantic diagnostics and cache them + */ + export function createSemanticDiagnosticsBuilder(options: BuilderOptions): SemanticDiagnosticsBuilder { + return createBuilder(options, BuilderType.SemanticDiagnosticsBuilder); + } + + /** + * Create the builder that can handle the changes in program and iterate through changed files + * to emit the those files and manage semantic diagnostics cache as well + */ + export function createEmitAndSemanticDiagnosticsBuilder(options: BuilderOptions): EmitAndSemanticDiagnosticsBuilder { + return createBuilder(options, BuilderType.EmitAndSemanticDiagnosticsBuilder); + } +} diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 07fb9203b6a..fcb4d98ace0 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -84,18 +84,17 @@ namespace ts { } /** - * Creates the function that compiles the program by maintaining the builder state and also return diagnostic reporter + * Creates the function that compiles the program by maintaining the builder for the program and reports the errors and emits files */ export function createProgramCompilerWithBuilderState(system = sys, reportDiagnostic?: DiagnosticReporter) { reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); - let builderState: Readonly | undefined; - const options: BuilderOptions = { + const builder = createEmitAndSemanticDiagnosticsBuilder({ getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), - computeHash: data => system.createHash ? system.createHash(data) : data - }; + computeHash: system.createHash ? system.createHash.bind(system) : identity + }); return (host: DirectoryStructureHost, program: Program) => { - builderState = createBuilderState(program, options, builderState); + builder.updateProgram(program); // First get and report any syntactic errors. const diagnostics = program.getSyntacticDiagnostics().slice(); @@ -118,14 +117,14 @@ namespace ts { let emitSkipped: boolean; let affectedEmitResult: AffectedFileEmitResult; - while (affectedEmitResult = builderState.emitNextAffectedFile(program, writeFile)) { + while (affectedEmitResult = builder.emitNextAffectedFile(program, writeFile)) { emitSkipped = emitSkipped || affectedEmitResult.emitSkipped; addRange(diagnostics, affectedEmitResult.diagnostics); sourceMaps = addRange(sourceMaps, affectedEmitResult.sourceMaps); } if (reportSemanticDiagnostics) { - addRange(diagnostics, builderState.getSemanticDiagnostics(program)); + addRange(diagnostics, builder.getSemanticDiagnostics(program)); } sortAndDeduplicateDiagnostics(diagnostics).forEach(reportDiagnostic); diff --git a/src/harness/unittests/builder.ts b/src/harness/unittests/builder.ts index 89b0852bd32..a9c16c59fbd 100644 --- a/src/harness/unittests/builder.ts +++ b/src/harness/unittests/builder.ts @@ -44,17 +44,16 @@ namespace ts { }); function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray) => void { - let builderState: BuilderState; - const builderOptions: BuilderOptions = { + const builder = createEmitAndSemanticDiagnosticsBuilder({ getCanonicalFileName: identity, computeHash: identity - }; + }); return fileNames => { const program = getProgram(); - builderState = createBuilderState(program, builderOptions, builderState); + builder.updateProgram(program); const outputFileNames: string[] = []; // tslint:disable-next-line no-empty - while (builderState.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName))) { + while (builder.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName))) { } assert.deepEqual(outputFileNames, fileNames); }; diff --git a/src/server/project.ts b/src/server/project.ts index 056fedf19af..3a5785163d2 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -139,7 +139,7 @@ namespace ts.server { /*@internal*/ resolutionCache: ResolutionCache; - private builderState: BuilderState; + private builder: InternalBuilder | undefined; /** * Set of files names that were updated since the last call to getChangesSinceVersion. */ @@ -451,11 +451,14 @@ namespace ts.server { return []; } this.updateGraph(); - this.builderState = createBuilderState(this.program, { - getCanonicalFileName: this.projectService.toCanonicalFileName, - computeHash: data => this.projectService.host.createHash(data) - }, this.builderState); - return mapDefined(this.builderState.getFilesAffectedBy(this.program, scriptInfo.path), + if (!this.builder) { + this.builder = createInternalBuilder({ + getCanonicalFileName: this.projectService.toCanonicalFileName, + computeHash: data => this.projectService.host.createHash(data) + }); + } + this.builder.updateProgram(this.program); + return mapDefined(this.builder.getFilesAffectedBy(this.program, scriptInfo.path), sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined); } @@ -491,7 +494,7 @@ namespace ts.server { } this.languageService.cleanupSemanticCache(); this.languageServiceEnabled = false; - this.builderState = undefined; + this.builder = undefined; this.resolutionCache.closeTypeRootsWatch(); this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ false); } @@ -532,7 +535,7 @@ namespace ts.server { this.rootFilesMap = undefined; this.externalFiles = undefined; this.program = undefined; - this.builderState = undefined; + this.builder = undefined; this.resolutionCache.clear(); this.resolutionCache = undefined; this.cachedUnresolvedImportsPerFile = undefined; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 5c306474533..e258d179310 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3771,40 +3771,48 @@ declare namespace ts { writeByteOrderMark: boolean; text: string; } + interface AffectedFileEmitResult extends EmitResult { + affectedFile?: SourceFile; + } + interface BuilderOptions { + getCanonicalFileName: (fileName: string) => string; + computeHash: (data: string) => string; + } /** - * State on which you can query affected files (files to save) and get semantic diagnostics(with their cache managed in the object) - * Note that it is only safe to pass BuilderState as old state when creating new state, when - * - If iterator's next method to get next affected file is never called - * - Iteration of single changed file and its dependencies (iteration through all of its affected files) is complete + * Builder to manage the program state changes */ - interface BuilderState { + interface BaseBuilder { /** - * The map of file infos, where there is entry for each file in the program - * The entry is signature of the file (from last emit) or empty string + * Updates the program in the builder to represent new state */ - fileInfos: ReadonlyMap>; + updateProgram(newProgram: Program): void; /** - * Returns true if module gerneration is not ModuleKind.None + * Get all the dependencies of the file */ - isModuleEmit: boolean; + getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; + } + /** + * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files + */ + interface SemanticDiagnosticsBuilder extends BaseBuilder { /** - * Map of file referenced or undefined if it wasnt module emit - * The entry is present only if file references other files - * The key is path of file and value is referenced map for that file (for every file referenced, there is entry in the set) + * Gets the semantic diagnostics from the program for the next affected file and caches it + * Returns undefined if the iteration is complete */ - referencedMap: ReadonlyMap | undefined; + getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray; /** - * Set of source file's paths that have been changed, either in resolution or versions + * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program + * The semantic diagnostics are cached and managed here + * Note that it is assumed that the when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics */ - changedFilesSet: ReadonlyMap; - /** - * Set of cached semantic diagnostics per file - */ - semanticDiagnosticsPerFile: ReadonlyMap>; - /** - * Returns true if this state is safe to use as oldState - */ - canCreateNewStateFrom(): boolean; + getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + } + /** + * The builder that can handle the changes in program and iterate through changed file to emit the files + * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files + */ + interface EmitAndSemanticDiagnosticsBuilder extends BaseBuilder { /** * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete */ @@ -3812,33 +3820,20 @@ declare namespace ts { /** * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program * The semantic diagnostics are cached and managed here - * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files + * Note that it is assumed that the when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics */ getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; - /** - * Get all the dependencies of the file - */ - getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; } /** - * Information about the source file: Its version and optional signature from last emit + * Create the builder to manage semantic diagnostics and cache them */ - interface FileInfo { - version: string; - signature: string; - } - interface AffectedFileEmitResult extends EmitResult { - affectedFile?: SourceFile; - } + function createSemanticDiagnosticsBuilder(options: BuilderOptions): SemanticDiagnosticsBuilder; /** - * Referenced files with values for the keys as referenced file's path to be true + * Create the builder that can handle the changes in program and iterate through changed files + * to emit the those files and manage semantic diagnostics cache as well */ - type ReferencedSet = ReadonlyMap; - interface BuilderOptions { - getCanonicalFileName: (fileName: string) => string; - computeHash: (data: string) => string; - } - function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly): BuilderState; + function createEmitAndSemanticDiagnosticsBuilder(options: BuilderOptions): EmitAndSemanticDiagnosticsBuilder; } declare namespace ts { function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; @@ -7278,7 +7273,7 @@ declare namespace ts.server { languageServiceEnabled: boolean; readonly trace?: (s: string) => void; readonly realpath?: (path: string) => string; - private builderState; + private builder; /** * Set of files names that were updated since the last call to getChangesSinceVersion. */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 2212a859c0a..bac53072302 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3718,40 +3718,48 @@ declare namespace ts { writeByteOrderMark: boolean; text: string; } + interface AffectedFileEmitResult extends EmitResult { + affectedFile?: SourceFile; + } + interface BuilderOptions { + getCanonicalFileName: (fileName: string) => string; + computeHash: (data: string) => string; + } /** - * State on which you can query affected files (files to save) and get semantic diagnostics(with their cache managed in the object) - * Note that it is only safe to pass BuilderState as old state when creating new state, when - * - If iterator's next method to get next affected file is never called - * - Iteration of single changed file and its dependencies (iteration through all of its affected files) is complete + * Builder to manage the program state changes */ - interface BuilderState { + interface BaseBuilder { /** - * The map of file infos, where there is entry for each file in the program - * The entry is signature of the file (from last emit) or empty string + * Updates the program in the builder to represent new state */ - fileInfos: ReadonlyMap>; + updateProgram(newProgram: Program): void; /** - * Returns true if module gerneration is not ModuleKind.None + * Get all the dependencies of the file */ - isModuleEmit: boolean; + getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; + } + /** + * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files + */ + interface SemanticDiagnosticsBuilder extends BaseBuilder { /** - * Map of file referenced or undefined if it wasnt module emit - * The entry is present only if file references other files - * The key is path of file and value is referenced map for that file (for every file referenced, there is entry in the set) + * Gets the semantic diagnostics from the program for the next affected file and caches it + * Returns undefined if the iteration is complete */ - referencedMap: ReadonlyMap | undefined; + getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray; /** - * Set of source file's paths that have been changed, either in resolution or versions + * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program + * The semantic diagnostics are cached and managed here + * Note that it is assumed that the when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics */ - changedFilesSet: ReadonlyMap; - /** - * Set of cached semantic diagnostics per file - */ - semanticDiagnosticsPerFile: ReadonlyMap>; - /** - * Returns true if this state is safe to use as oldState - */ - canCreateNewStateFrom(): boolean; + getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + } + /** + * The builder that can handle the changes in program and iterate through changed file to emit the files + * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files + */ + interface EmitAndSemanticDiagnosticsBuilder extends BaseBuilder { /** * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete */ @@ -3759,33 +3767,20 @@ declare namespace ts { /** * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program * The semantic diagnostics are cached and managed here - * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files + * Note that it is assumed that the when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics */ getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; - /** - * Get all the dependencies of the file - */ - getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; } /** - * Information about the source file: Its version and optional signature from last emit + * Create the builder to manage semantic diagnostics and cache them */ - interface FileInfo { - version: string; - signature: string; - } - interface AffectedFileEmitResult extends EmitResult { - affectedFile?: SourceFile; - } + function createSemanticDiagnosticsBuilder(options: BuilderOptions): SemanticDiagnosticsBuilder; /** - * Referenced files with values for the keys as referenced file's path to be true + * Create the builder that can handle the changes in program and iterate through changed files + * to emit the those files and manage semantic diagnostics cache as well */ - type ReferencedSet = ReadonlyMap; - interface BuilderOptions { - getCanonicalFileName: (fileName: string) => string; - computeHash: (data: string) => string; - } - function createBuilderState(newProgram: Program, options: BuilderOptions, oldState?: Readonly): BuilderState; + function createEmitAndSemanticDiagnosticsBuilder(options: BuilderOptions): EmitAndSemanticDiagnosticsBuilder; } declare namespace ts { function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; @@ -3819,7 +3814,7 @@ declare namespace ts { declare namespace ts { type DiagnosticReporter = (diagnostic: Diagnostic) => void; /** - * Creates the function that compiles the program by maintaining the builder state and also return diagnostic reporter + * Creates the function that compiles the program by maintaining the builder for the program and reports the errors and emits files */ function createProgramCompilerWithBuilderState(system?: System, reportDiagnostic?: DiagnosticReporter): (host: DirectoryStructureHost, program: Program) => void; interface WatchHost { From e102fee363e256cd14e2881f6912e78b4e369333 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 14 Nov 2017 16:53:07 -0800 Subject: [PATCH 013/172] Use the results from affected file enumerator apis as Affected File result --- src/compiler/builder.ts | 35 ++++++++++++------- src/compiler/watch.ts | 8 ++--- .../reference/api/tsserverlibrary.d.ts | 9 ++--- tests/baselines/reference/api/typescript.d.ts | 9 ++--- 4 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 8703d83aa1d..40aabb04baa 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -331,11 +331,18 @@ namespace ts { } } + /** + * Returns the result with affected file + */ + function toAffectedFileResult(result: T, affectedFile?: SourceFile): AffectedFileResult { + return { result, affectedFile }; + } + /** * Emits the next affected file, and returns the EmitResult along with source files emitted * Returns undefined when iteration is complete */ - function emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined { + function emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult { const affectedFile = getNextAffectedFile(programOfThisState); if (!affectedFile) { // Done @@ -343,21 +350,22 @@ namespace ts { } else if (affectedFile === programOfThisState) { // When whole program is affected, do emit only once (eg when --out or --outFile is specified) - return programOfThisState.emit(/*targetSourceFile*/ undefined, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers); + return toAffectedFileResult(programOfThisState.emit(/*targetSourceFile*/ undefined, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers)); } // Emit the affected file const targetSourceFile = affectedFile as SourceFile; - const result = programOfThisState.emit(targetSourceFile, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers) as AffectedFileEmitResult; - result.affectedFile = targetSourceFile; - return result; + return toAffectedFileResult( + programOfThisState.emit(targetSourceFile, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers), + targetSourceFile + ); } /** * Return the semantic diagnostics for the next affected file or undefined if iteration is complete * If provided ignoreSourceFile would be called before getting the diagnostics and would ignore the sourceFile if the returned value was true */ - function getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray { + function getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult> { while (true) { const affectedFile = getNextAffectedFile(programOfThisState); if (!affectedFile) { @@ -366,7 +374,7 @@ namespace ts { } else if (affectedFile === programOfThisState) { // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified) - return programOfThisState.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken); + return toAffectedFileResult(programOfThisState.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken)); } // Get diagnostics for the affected file if its not ignored @@ -376,7 +384,10 @@ namespace ts { continue; } - return getSemanticDiagnosticsOfFile(programOfThisState, targetSourceFile, cancellationToken); + return toAffectedFileResult( + getSemanticDiagnosticsOfFile(programOfThisState, targetSourceFile, cancellationToken), + targetSourceFile + ); } } @@ -682,9 +693,7 @@ namespace ts { text: string; } - export interface AffectedFileEmitResult extends EmitResult { - affectedFile?: SourceFile; - } + export type AffectedFileResult = { result: T; affectedFile?: SourceFile; } | undefined; export interface BuilderOptions { getCanonicalFileName: (fileName: string) => string; @@ -714,7 +723,7 @@ namespace ts { * Gets the semantic diagnostics from the program for the next affected file and caches it * Returns undefined if the iteration is complete */ - getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray; + getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; /** * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program @@ -733,7 +742,7 @@ namespace ts { /** * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete */ - emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined; + emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult; /** * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index fcb4d98ace0..716846cd606 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -116,11 +116,11 @@ namespace ts { let sourceMaps: SourceMapData[]; let emitSkipped: boolean; - let affectedEmitResult: AffectedFileEmitResult; + let affectedEmitResult: AffectedFileResult; while (affectedEmitResult = builder.emitNextAffectedFile(program, writeFile)) { - emitSkipped = emitSkipped || affectedEmitResult.emitSkipped; - addRange(diagnostics, affectedEmitResult.diagnostics); - sourceMaps = addRange(sourceMaps, affectedEmitResult.sourceMaps); + emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped; + addRange(diagnostics, affectedEmitResult.result.diagnostics); + sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps); } if (reportSemanticDiagnostics) { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index e258d179310..e5ee4d8db6c 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3771,9 +3771,10 @@ declare namespace ts { writeByteOrderMark: boolean; text: string; } - interface AffectedFileEmitResult extends EmitResult { + type AffectedFileResult = { + result: T; affectedFile?: SourceFile; - } + } | undefined; interface BuilderOptions { getCanonicalFileName: (fileName: string) => string; computeHash: (data: string) => string; @@ -3799,7 +3800,7 @@ declare namespace ts { * Gets the semantic diagnostics from the program for the next affected file and caches it * Returns undefined if the iteration is complete */ - getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray; + getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; /** * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program * The semantic diagnostics are cached and managed here @@ -3816,7 +3817,7 @@ declare namespace ts { /** * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete */ - emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined; + emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult; /** * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program * The semantic diagnostics are cached and managed here diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index bac53072302..bd1eff0be6f 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3718,9 +3718,10 @@ declare namespace ts { writeByteOrderMark: boolean; text: string; } - interface AffectedFileEmitResult extends EmitResult { + type AffectedFileResult = { + result: T; affectedFile?: SourceFile; - } + } | undefined; interface BuilderOptions { getCanonicalFileName: (fileName: string) => string; computeHash: (data: string) => string; @@ -3746,7 +3747,7 @@ declare namespace ts { * Gets the semantic diagnostics from the program for the next affected file and caches it * Returns undefined if the iteration is complete */ - getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): ReadonlyArray; + getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; /** * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program * The semantic diagnostics are cached and managed here @@ -3763,7 +3764,7 @@ declare namespace ts { /** * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete */ - emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileEmitResult | undefined; + emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult; /** * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program * The semantic diagnostics are cached and managed here From ffa64e8c4f1ca31263e0e16e299b0542f633fc65 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 16 Nov 2017 11:16:39 -0800 Subject: [PATCH 014/172] Set program as affected if emitting/diagnostics for whole program --- src/compiler/builder.ts | 16 +++++++++++----- .../baselines/reference/api/tsserverlibrary.d.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 40aabb04baa..69f60bb1708 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -334,8 +334,8 @@ namespace ts { /** * Returns the result with affected file */ - function toAffectedFileResult(result: T, affectedFile?: SourceFile): AffectedFileResult { - return { result, affectedFile }; + function toAffectedFileResult(result: T, affected: SourceFile | Program): AffectedFileResult { + return { result, affected }; } /** @@ -350,7 +350,10 @@ namespace ts { } else if (affectedFile === programOfThisState) { // When whole program is affected, do emit only once (eg when --out or --outFile is specified) - return toAffectedFileResult(programOfThisState.emit(/*targetSourceFile*/ undefined, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers)); + return toAffectedFileResult( + programOfThisState.emit(/*targetSourceFile*/ undefined, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers), + programOfThisState + ); } // Emit the affected file @@ -374,7 +377,10 @@ namespace ts { } else if (affectedFile === programOfThisState) { // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified) - return toAffectedFileResult(programOfThisState.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken)); + return toAffectedFileResult( + programOfThisState.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken), + programOfThisState + ); } // Get diagnostics for the affected file if its not ignored @@ -693,7 +699,7 @@ namespace ts { text: string; } - export type AffectedFileResult = { result: T; affectedFile?: SourceFile; } | undefined; + export type AffectedFileResult = { result: T; affected: SourceFile | Program; } | undefined; export interface BuilderOptions { getCanonicalFileName: (fileName: string) => string; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index e5ee4d8db6c..de738e7cbc0 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3773,7 +3773,7 @@ declare namespace ts { } type AffectedFileResult = { result: T; - affectedFile?: SourceFile; + affected: SourceFile | Program; } | undefined; interface BuilderOptions { getCanonicalFileName: (fileName: string) => string; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index bd1eff0be6f..974bc043d3f 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3720,7 +3720,7 @@ declare namespace ts { } type AffectedFileResult = { result: T; - affectedFile?: SourceFile; + affected: SourceFile | Program; } | undefined; interface BuilderOptions { getCanonicalFileName: (fileName: string) => string; From 012f12bcbd001165f3f6216105b92eb0aa6f326b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 22 Nov 2017 18:24:53 -0800 Subject: [PATCH 015/172] To handle cancellation token, remove changed/affected files from the changeset only after getting the result --- src/compiler/builder.ts | 100 +++++++++++++++++++++---------- src/compiler/program.ts | 2 +- src/harness/unittests/builder.ts | 72 +++++++++++++++++++++- src/server/project.ts | 2 +- 4 files changed, 141 insertions(+), 35 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 69f60bb1708..ddbb015a2ee 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -22,7 +22,7 @@ namespace ts { * This api is only for internal use */ /*@internal*/ - getFilesAffectedBy(programOfThisState: Program, path: Path): ReadonlyArray; + getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken): ReadonlyArray; } /** @@ -86,7 +86,7 @@ namespace ts { * Get the files affected by the source file. * This is dependent on whether its a module emit or not and hence function expression */ - let getEmitDependentFilesAffectedBy: (programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map | undefined) => ReadonlyArray; + let getEmitDependentFilesAffectedBy: (programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) => ReadonlyArray; /** * Cache of semantic diagnostics for files with their Path being the key @@ -272,38 +272,65 @@ namespace ts { /** * Gets the files affected by the path from the program */ - function getFilesAffectedBy(programOfThisState: Program, path: Path, cacheToUpdateSignature?: Map): ReadonlyArray { + function getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, cacheToUpdateSignature?: Map): ReadonlyArray { + // Since the operation could be cancelled, the signatures are always stored in the cache + // They will be commited once it is safe to use them + // eg when calling this api from tsserver, if there is no cancellation of the operation + // In the other cases the affected files signatures are commited only after the iteration through the result is complete + const signatureCache = cacheToUpdateSignature || createMap(); const sourceFile = programOfThisState.getSourceFileByPath(path); if (!sourceFile) { return emptyArray; } - if (!updateShapeSignature(programOfThisState, sourceFile, cacheToUpdateSignature)) { + if (!updateShapeSignature(programOfThisState, sourceFile, signatureCache, cancellationToken)) { return [sourceFile]; } - return getEmitDependentFilesAffectedBy(programOfThisState, sourceFile, cacheToUpdateSignature); + const result = getEmitDependentFilesAffectedBy(programOfThisState, sourceFile, signatureCache, cancellationToken); + if (!cacheToUpdateSignature) { + // Commit all the signatures in the signature cache + updateSignaturesFromCache(signatureCache); + } + return result; } - function getNextAffectedFile(programOfThisState: Program): SourceFile | Program | undefined { + /** + * Updates the signatures from the cache + * This should be called whenever it is safe to commit the state of the builder + */ + function updateSignaturesFromCache(signatureCache: Map) { + signatureCache.forEach((signature, path) => { + fileInfos.get(path).signature = signature; + hasCalledUpdateShapeSignature.set(path, true); + }); + } + + /** + * This function returns the next affected file to be processed. + * Note that until doneAffected is called it would keep reporting same result + * This is to allow the callers to be able to actually remove affected file only when the operation is complete + * eg. if during diagnostics check cancellation token ends up cancelling the request, the affected file should be retained + */ + function getNextAffectedFile(programOfThisState: Program, cancellationToken: CancellationToken | undefined): SourceFile | Program | undefined { while (true) { if (affectedFiles) { while (affectedFilesIndex < affectedFiles.length) { const affectedFile = affectedFiles[affectedFilesIndex]; - affectedFilesIndex++; if (!seenAffectedFiles.has(affectedFile.path)) { // Set the next affected file as seen and remove the cached semantic diagnostics - seenAffectedFiles.set(affectedFile.path, true); semanticDiagnosticsPerFile.delete(affectedFile.path); return affectedFile; } + seenAffectedFiles.set(affectedFile.path, true); + affectedFilesIndex++; } // Remove the changed file from the change set changedFilesSet.delete(currentChangedFilePath); currentChangedFilePath = undefined; // Commit the changes in file signature - currentAffectedFilesSignatures.forEach((signature, path) => fileInfos.get(path).signature = signature); + updateSignaturesFromCache(currentAffectedFilesSignatures); currentAffectedFilesSignatures.clear(); affectedFiles = undefined; } @@ -320,21 +347,37 @@ namespace ts { // so operations are performed directly on program, return program if (compilerOptions.outFile || compilerOptions.out) { Debug.assert(semanticDiagnosticsPerFile.size === 0); - changedFilesSet.clear(); return programOfThisState; } // Get next batch of affected files + currentAffectedFilesSignatures.clear(); + affectedFiles = getFilesAffectedBy(programOfThisState, nextKey.value as Path, cancellationToken, currentAffectedFilesSignatures); currentChangedFilePath = nextKey.value as Path; + semanticDiagnosticsPerFile.delete(currentChangedFilePath); affectedFilesIndex = 0; - affectedFiles = getFilesAffectedBy(programOfThisState, nextKey.value as Path, currentAffectedFilesSignatures); + } + } + + /** + * This is called after completing operation on the next affected file. + * The operations here are postponed to ensure that cancellation during the iteration is handled correctly + */ + function doneWithAffectedFile(programOfThisState: Program, affected: SourceFile | Program) { + if (affected === programOfThisState) { + changedFilesSet.clear(); + } + else { + seenAffectedFiles.set((affected).path, true); + affectedFilesIndex++; } } /** * Returns the result with affected file */ - function toAffectedFileResult(result: T, affected: SourceFile | Program): AffectedFileResult { + function toAffectedFileResult(programOfThisState: Program, result: T, affected: SourceFile | Program): AffectedFileResult { + doneWithAffectedFile(programOfThisState, affected); return { result, affected }; } @@ -343,7 +386,7 @@ namespace ts { * Returns undefined when iteration is complete */ function emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult { - const affectedFile = getNextAffectedFile(programOfThisState); + const affectedFile = getNextAffectedFile(programOfThisState, cancellationToken); if (!affectedFile) { // Done return undefined; @@ -351,6 +394,7 @@ namespace ts { else if (affectedFile === programOfThisState) { // When whole program is affected, do emit only once (eg when --out or --outFile is specified) return toAffectedFileResult( + programOfThisState, programOfThisState.emit(/*targetSourceFile*/ undefined, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers), programOfThisState ); @@ -359,6 +403,7 @@ namespace ts { // Emit the affected file const targetSourceFile = affectedFile as SourceFile; return toAffectedFileResult( + programOfThisState, programOfThisState.emit(targetSourceFile, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers), targetSourceFile ); @@ -370,7 +415,7 @@ namespace ts { */ function getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult> { while (true) { - const affectedFile = getNextAffectedFile(programOfThisState); + const affectedFile = getNextAffectedFile(programOfThisState, cancellationToken); if (!affectedFile) { // Done return undefined; @@ -378,6 +423,7 @@ namespace ts { else if (affectedFile === programOfThisState) { // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified) return toAffectedFileResult( + programOfThisState, programOfThisState.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken), programOfThisState ); @@ -387,10 +433,12 @@ namespace ts { const targetSourceFile = affectedFile as SourceFile; if (ignoreSourceFile && ignoreSourceFile(targetSourceFile)) { // Get next affected file + doneWithAffectedFile(programOfThisState, targetSourceFile); continue; } return toAffectedFileResult( + programOfThisState, getSemanticDiagnosticsOfFile(programOfThisState, targetSourceFile, cancellationToken), targetSourceFile ); @@ -505,16 +553,14 @@ namespace ts { * Returns if the shape of the signature has changed since last emit * Note that it also updates the current signature as the latest signature for the file */ - function updateShapeSignature(program: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map | undefined) { + function updateShapeSignature(program: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) { Debug.assert(!!sourceFile); // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate - if (hasCalledUpdateShapeSignature.has(sourceFile.path)) { + if (hasCalledUpdateShapeSignature.has(sourceFile.path) || cacheToUpdateSignature.has(sourceFile.path)) { return false; } - Debug.assert(!cacheToUpdateSignature || !cacheToUpdateSignature.has(sourceFile.path)); - hasCalledUpdateShapeSignature.set(sourceFile.path, true); const info = fileInfos.get(sourceFile.path); Debug.assert(!!info); @@ -522,29 +568,19 @@ namespace ts { let latestSignature: string; if (sourceFile.isDeclarationFile) { latestSignature = sourceFile.version; - setLatestSigature(); } else { - const emitOutput = getFileEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true); + const emitOutput = getFileEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken); if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { latestSignature = options.computeHash(emitOutput.outputFiles[0].text); - setLatestSigature(); } else { latestSignature = prevSignature; } } + cacheToUpdateSignature.set(sourceFile.path, latestSignature); return !prevSignature || latestSignature !== prevSignature; - - function setLatestSigature() { - if (cacheToUpdateSignature) { - cacheToUpdateSignature.set(sourceFile.path, latestSignature); - } - else { - info.signature = latestSignature; - } - } } /** @@ -652,7 +688,7 @@ namespace ts { /** * When program emits modular code, gets the files affected by the sourceFile whose shape has changed */ - function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map | undefined) { + function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) { if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) { return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); } @@ -675,7 +711,7 @@ namespace ts { if (!seenFileNamesMap.has(currentPath)) { const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath); seenFileNamesMap.set(currentPath, currentSourceFile); - if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile, cacheToUpdateSignature)) { + if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken)) { queue.push(...getReferencedByPaths(currentPath)); } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 4088a6951a1..213b1fc9601 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1162,7 +1162,7 @@ namespace ts { // This is because in the -out scenario all files need to be emitted, and therefore all // files need to be type checked. And the way to specify that all files need to be type // checked is to not pass the file to getEmitResolver. - const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile); + const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile, cancellationToken); performance.mark("beforeEmit"); diff --git a/src/harness/unittests/builder.ts b/src/harness/unittests/builder.ts index a9c16c59fbd..64f43be1376 100644 --- a/src/harness/unittests/builder.ts +++ b/src/harness/unittests/builder.ts @@ -41,9 +41,39 @@ namespace ts { program = updateProgramFile(program, "/b.ts", "namespace B { export const x = 1; }"); assertChanges(["/b.js", "/a.js"]); }); + + it("keeps the file in affected files if cancellation token throws during the operation", () => { + const files: NamedSourceText[] = [ + { name: "/a.ts", text: SourceText.New("", 'import { b } from "./b";', "") }, + { name: "/b.ts", text: SourceText.New("", ' import { c } from "./c";', "export const b = c;") }, + { name: "/c.ts", text: SourceText.New("", "", "export const c = 0;") }, + { name: "/d.ts", text: SourceText.New("", "", "export const dd = 0;") }, + { name: "/e.ts", text: SourceText.New("", "", "export const ee = 0;") }, + ]; + + let program = newProgram(files, ["/d.ts", "/e.ts", "/a.ts"], {}); + const assertChanges = makeAssertChangesWithCancellationToken(() => program); + // No cancellation + assertChanges(["/d.js", "/e.js", "/c.js", "/b.js", "/a.js"]); + + // cancel when emitting a.ts + program = updateProgramFile(program, "/a.ts", "export function foo() { }"); + assertChanges(["/a.js"], 0); + // Change d.ts and verify previously pending a.ts is emitted as well + program = updateProgramFile(program, "/d.ts", "export function bar() { }"); + assertChanges(["/a.js", "/d.js"]); + + // Cancel when emitting b.js + program = updateProgramFile(program, "/b.ts", "export class b { foo() { c + 1; } }"); + program = updateProgramFile(program, "/d.ts", "export function bar2() { }"); + assertChanges(["/d.js", "/b.js", "/a.js"], 1); + // Change e.ts and verify previously b.js as well as a.js get emitted again since previous change was consumed completely but not d.ts + program = updateProgramFile(program, "/e.ts", "export function bar3() { }"); + assertChanges(["/b.js", "/a.js", "/e.js"]); + }); }); - function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray) => void { + function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray) => void { const builder = createEmitAndSemanticDiagnosticsBuilder({ getCanonicalFileName: identity, computeHash: identity @@ -59,6 +89,46 @@ namespace ts { }; } + function makeAssertChangesWithCancellationToken(getProgram: () => Program): (fileNames: ReadonlyArray, cancelAfterEmitLength?: number) => void { + const builder = createEmitAndSemanticDiagnosticsBuilder({ + getCanonicalFileName: identity, + computeHash: identity + }); + let cancel = false; + const cancellationToken: CancellationToken = { + isCancellationRequested: () => cancel, + throwIfCancellationRequested: () => { + if (cancel) { + throw new OperationCanceledException(); + } + }, + }; + return (fileNames, cancelAfterEmitLength?: number) => { + cancel = false; + let operationWasCancelled = false; + const program = getProgram(); + builder.updateProgram(program); + const outputFileNames: string[] = []; + try { + // tslint:disable-next-line no-empty + do { + assert.isFalse(cancel); + if (outputFileNames.length === cancelAfterEmitLength) { + cancel = true; + } + } while (builder.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName), cancellationToken)); + } + catch (e) { + assert.isFalse(operationWasCancelled); + assert.isTrue(e instanceof OperationCanceledException, e.toString()); + operationWasCancelled = true; + } + assert.equal(cancel, operationWasCancelled); + assert.equal(operationWasCancelled, fileNames.length > cancelAfterEmitLength); + assert.deepEqual(outputFileNames, fileNames.slice(0, cancelAfterEmitLength)); + }; + } + function updateProgramFile(program: ProgramWithSourceTexts, fileName: string, fileContent: string): ProgramWithSourceTexts { return updateProgram(program, program.getRootFileNames(), program.getCompilerOptions(), files => { updateProgramText(files, fileName, fileContent); diff --git a/src/server/project.ts b/src/server/project.ts index 3a5785163d2..9b9dbb99436 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -458,7 +458,7 @@ namespace ts.server { }); } this.builder.updateProgram(this.program); - return mapDefined(this.builder.getFilesAffectedBy(this.program, scriptInfo.path), + return mapDefined(this.builder.getFilesAffectedBy(this.program, scriptInfo.path, this.cancellationToken), sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined); } From 0b79f4a0735c222e21e307ebbcfa6112ff12a89c Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 22 Nov 2017 18:34:49 -0800 Subject: [PATCH 016/172] Handle emit only declaration file to always produce declaration file and skip the diagnostics check --- src/compiler/checker.ts | 6 ++-- src/compiler/declarationEmitter.ts | 2 +- src/compiler/program.ts | 52 ++++++++++++++++-------------- src/compiler/types.ts | 2 +- src/harness/unittests/builder.ts | 7 ++++ 5 files changed, 40 insertions(+), 29 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bc9906f2054..364af70cb55 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -564,10 +564,12 @@ namespace ts { return _jsxNamespace; } - function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) { + function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken, ignoreDiagnostics?: boolean) { // Ensure we have all the type information in place for this file so that all the // emitter questions of this resolver will return the right information. - getDiagnostics(sourceFile, cancellationToken); + if (!ignoreDiagnostics) { + getDiagnostics(sourceFile, cancellationToken); + } return emitResolver; } diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index dc76e0ddf50..90d9ff3de5a 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -1994,7 +1994,7 @@ namespace ts { export function writeDeclarationFile(declarationFilePath: string, sourceFileOrBundle: SourceFile | Bundle, host: EmitHost, resolver: EmitResolver, emitterDiagnostics: DiagnosticCollection, emitOnlyDtsFiles: boolean) { const emitDeclarationResult = emitDeclarations(host, resolver, emitterDiagnostics, declarationFilePath, sourceFileOrBundle, emitOnlyDtsFiles); const emitSkipped = emitDeclarationResult.reportedDeclarationError || host.isEmitBlocked(declarationFilePath) || host.getCompilerOptions().noEmit; - if (!emitSkipped) { + if (!emitSkipped || emitOnlyDtsFiles) { const sourceFiles = sourceFileOrBundle.kind === SyntaxKind.Bundle ? sourceFileOrBundle.sourceFiles : [sourceFileOrBundle]; const declarationOutput = emitDeclarationResult.referencesOutput + getDeclarationOutput(emitDeclarationResult.synchronousDeclarationOutput, emitDeclarationResult.moduleElementDeclarationEmitInfo); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 213b1fc9601..9560859c7ce 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1125,32 +1125,34 @@ namespace ts { function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult { let declarationDiagnostics: ReadonlyArray = []; - if (options.noEmit) { - return { diagnostics: declarationDiagnostics, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true }; - } - - // If the noEmitOnError flag is set, then check if we have any errors so far. If so, - // immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we - // get any preEmit diagnostics, not just the ones - if (options.noEmitOnError) { - const diagnostics = [ - ...program.getOptionsDiagnostics(cancellationToken), - ...program.getSyntacticDiagnostics(sourceFile, cancellationToken), - ...program.getGlobalDiagnostics(cancellationToken), - ...program.getSemanticDiagnostics(sourceFile, cancellationToken) - ]; - - if (diagnostics.length === 0 && program.getCompilerOptions().declaration) { - declarationDiagnostics = program.getDeclarationDiagnostics(/*sourceFile*/ undefined, cancellationToken); + if (!emitOnlyDtsFiles) { + if (options.noEmit) { + return { diagnostics: declarationDiagnostics, sourceMaps: undefined, emittedFiles: undefined, emitSkipped: true }; } - if (diagnostics.length > 0 || declarationDiagnostics.length > 0) { - return { - diagnostics: concatenate(diagnostics, declarationDiagnostics), - sourceMaps: undefined, - emittedFiles: undefined, - emitSkipped: true - }; + // If the noEmitOnError flag is set, then check if we have any errors so far. If so, + // immediately bail out. Note that we pass 'undefined' for 'sourceFile' so that we + // get any preEmit diagnostics, not just the ones + if (options.noEmitOnError) { + const diagnostics = [ + ...program.getOptionsDiagnostics(cancellationToken), + ...program.getSyntacticDiagnostics(sourceFile, cancellationToken), + ...program.getGlobalDiagnostics(cancellationToken), + ...program.getSemanticDiagnostics(sourceFile, cancellationToken) + ]; + + if (diagnostics.length === 0 && program.getCompilerOptions().declaration) { + declarationDiagnostics = program.getDeclarationDiagnostics(/*sourceFile*/ undefined, cancellationToken); + } + + if (diagnostics.length > 0 || declarationDiagnostics.length > 0) { + return { + diagnostics: concatenate(diagnostics, declarationDiagnostics), + sourceMaps: undefined, + emittedFiles: undefined, + emitSkipped: true + }; + } } } @@ -1162,7 +1164,7 @@ namespace ts { // This is because in the -out scenario all files need to be emitted, and therefore all // files need to be type checked. And the way to specify that all files need to be type // checked is to not pass the file to getEmitResolver. - const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile, cancellationToken); + const emitResolver = getDiagnosticsProducingTypeChecker().getEmitResolver((options.outFile || options.out) ? undefined : sourceFile, cancellationToken, emitOnlyDtsFiles); performance.mark("beforeEmit"); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 53c36ac6caa..568a3e2afe4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2798,7 +2798,7 @@ namespace ts { // Should not be called directly. Should only be accessed through the Program instance. /* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; /* @internal */ getGlobalDiagnostics(): Diagnostic[]; - /* @internal */ getEmitResolver(sourceFile?: SourceFile, cancellationToken?: CancellationToken): EmitResolver; + /* @internal */ getEmitResolver(sourceFile?: SourceFile, cancellationToken?: CancellationToken, ignoreDiagnostics?: boolean): EmitResolver; /* @internal */ getNodeCount(): number; /* @internal */ getIdentifierCount(): number; diff --git a/src/harness/unittests/builder.ts b/src/harness/unittests/builder.ts index 64f43be1376..38ac8a4e3d7 100644 --- a/src/harness/unittests/builder.ts +++ b/src/harness/unittests/builder.ts @@ -70,6 +70,13 @@ namespace ts { // Change e.ts and verify previously b.js as well as a.js get emitted again since previous change was consumed completely but not d.ts program = updateProgramFile(program, "/e.ts", "export function bar3() { }"); assertChanges(["/b.js", "/a.js", "/e.js"]); + + // Cancel in the middle of affected files list after b.js emit + program = updateProgramFile(program, "/b.ts", "export class b { foo2() { c + 1; } }"); + assertChanges(["/b.js", "/a.js"], 1); + // Change e.ts and verify previously b.js as well as a.js get emitted again since previous change was consumed completely but not d.ts + program = updateProgramFile(program, "/e.ts", "export function bar5() { }"); + assertChanges(["/b.js", "/a.js", "/e.js"]); }); }); From 3dda2179e8cf7d0b4c6fa474b98c5e10991f5813 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 4 Dec 2017 14:35:37 -0800 Subject: [PATCH 017/172] Rename getProgram to getExistingProgram --- src/compiler/watch.ts | 8 ++++---- src/harness/unittests/reuseProgramStructure.ts | 4 ++-- src/harness/unittests/tscWatchMode.ts | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 5a82584c8c3..70aa8939bcc 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -214,9 +214,9 @@ namespace ts { export interface Watch { /** Synchronize the program with the changes */ synchronizeProgram(): void; - /** Get current program */ + /** Gets the existing program without synchronizing with changes on host */ /*@internal*/ - getProgram(): Program; + getExistingProgram(): Program; } /** @@ -360,8 +360,8 @@ namespace ts { watchConfigFileWildCardDirectories(); return configFileName ? - { getProgram: () => program, synchronizeProgram } : - { getProgram: () => program, synchronizeProgram, updateRootFileNames }; + { getExistingProgram: () => program, synchronizeProgram } : + { getExistingProgram: () => program, synchronizeProgram, updateRootFileNames }; function synchronizeProgram() { writeLog(`Synchronizing program`); diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index cc330300d33..108e2d86694 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -897,12 +897,12 @@ namespace ts { } function verifyProgramWithoutConfigFile(system: System, rootFiles: string[], options: CompilerOptions) { - const program = createWatchOfFilesAndCompilerOptions(rootFiles, options, system).getProgram(); + const program = createWatchOfFilesAndCompilerOptions(rootFiles, options, system).getExistingProgram(); verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options)); } function verifyProgramWithConfigFile(system: System, configFileName: string) { - const program = createWatchOfConfigFile(configFileName, {}, system).getProgram(); + const program = createWatchOfConfigFile(configFileName, {}, system).getExistingProgram(); const { fileNames, options } = parseConfigFile(configFileName, {}, system, notImplemented); verifyProgramIsUptoDate(program, fileNames, options); } diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index 6a83d950ad3..736f8c520b3 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -24,12 +24,12 @@ namespace ts.tscWatch { function createWatchOfConfigFile(configFileName: string, host: WatchedSystem) { const watch = ts.createWatchOfConfigFile(configFileName, {}, host); - return () => watch.getProgram(); + return () => watch.getExistingProgram(); } function createWatchOfFilesAndCompilerOptions(rootFiles: string[], host: WatchedSystem, options: CompilerOptions = {}) { const watch = ts.createWatchOfFilesAndCompilerOptions(rootFiles, options, host); - return () => watch.getProgram(); + return () => watch.getExistingProgram(); } function getEmittedLineForMultiFileOutput(file: FileOrFolder, host: WatchedSystem) { @@ -237,8 +237,8 @@ namespace ts.tscWatch { const host = createWatchedSystem([configFile, libFile, file1, file2, file3]); const watch = ts.createWatchOfConfigFile(configFile.path, {}, host, notImplemented); - checkProgramActualFiles(watch.getProgram(), [file1.path, libFile.path, file2.path]); - checkProgramRootFiles(watch.getProgram(), [file1.path, file2.path]); + checkProgramActualFiles(watch.getExistingProgram(), [file1.path, libFile.path, file2.path]); + checkProgramRootFiles(watch.getExistingProgram(), [file1.path, file2.path]); checkWatchedFiles(host, [configFile.path, file1.path, file2.path, libFile.path]); const configDir = getDirectoryPath(configFile.path); checkWatchedDirectories(host, [configDir, combinePaths(configDir, projectSystem.nodeModulesAtTypes)], /*recursive*/ true); From 61fc9b94de0b7e40030d41d2893e7a61817656e4 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 4 Dec 2017 14:40:30 -0800 Subject: [PATCH 018/172] Rename Watch.synchronizeProgram to getProgram and return the updated program as part of this api --- src/compiler/watch.ts | 16 +++++++++------- tests/baselines/reference/api/typescript.d.ts | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 70aa8939bcc..e00a6751a70 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -212,8 +212,8 @@ namespace ts { } export interface Watch { - /** Synchronize the program with the changes */ - synchronizeProgram(): void; + /** Synchronize with host and get updated program */ + getProgram(): Program; /** Gets the existing program without synchronizing with changes on host */ /*@internal*/ getExistingProgram(): Program; @@ -360,10 +360,10 @@ namespace ts { watchConfigFileWildCardDirectories(); return configFileName ? - { getExistingProgram: () => program, synchronizeProgram } : - { getExistingProgram: () => program, synchronizeProgram, updateRootFileNames }; + { getExistingProgram: () => program, getProgram: synchronizeProgram } : + { getExistingProgram: () => program, getProgram: synchronizeProgram, updateRootFileNames }; - function synchronizeProgram() { + function synchronizeProgram(): Program { writeLog(`Synchronizing program`); if (hasChangedCompilerOptions) { @@ -375,7 +375,7 @@ namespace ts { const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(); if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames)) { - return; + return program; } beforeProgramCreate(compilerOptions); @@ -411,6 +411,7 @@ namespace ts { afterProgramCreate(directoryStructureHost, program); reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); + return program; } function updateRootFileNames(files: string[]) { @@ -569,7 +570,8 @@ namespace ts { case ConfigFileProgramReloadLevel.Full: return reloadConfigFile(); default: - return synchronizeProgram(); + synchronizeProgram(); + return; } } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 3ea22f19902..df02d53209c 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3850,8 +3850,8 @@ declare namespace ts { onConfigFileDiagnostic(diagnostic: Diagnostic): void; } interface Watch { - /** Synchronize the program with the changes */ - synchronizeProgram(): void; + /** Synchronize with host and get updated program */ + getProgram(): Program; } /** * Creates the watch what generates program using the config file From 471c83b7f59e7df61af49e44811049ebdd90fd00 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 4 Dec 2017 15:08:56 -0800 Subject: [PATCH 019/172] Rename WatchHost.moduleNameResolver to WatchHost.resolveModuleNames to align with compiler host --- src/compiler/resolutionCache.ts | 8 ++++---- src/compiler/watch.ts | 15 ++++++++------- src/server/project.ts | 4 ++-- tests/baselines/reference/api/typescript.d.ts | 2 +- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index e21e81a2e88..1c96dc21f08 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -9,7 +9,7 @@ namespace ts { startRecordingFilesWithChangedResolutions(): void; finishRecordingFilesWithChangedResolutions(): Path[]; - resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, logChanges: boolean): ResolvedModuleFull[]; + resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined): ResolvedModuleFull[]; resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; invalidateResolutionOfFile(filePath: Path): void; @@ -72,7 +72,7 @@ namespace ts { type GetResolutionWithResolvedFileName = (resolution: T) => R; - export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string): ResolutionCache { + export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string, logChangesWhenResolvingModule: boolean): ResolutionCache { let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; let filesWithInvalidatedResolutions: Map | undefined; let allFilesHaveInvalidatedResolution = false; @@ -306,12 +306,12 @@ namespace ts { ); } - function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, logChanges: boolean): ResolvedModuleFull[] { + function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined): ResolvedModuleFull[] { return resolveNamesWithLocalCache( moduleNames, containingFile, resolvedModuleNames, perDirectoryResolvedModuleNames, resolveModuleName, getResolvedModule, - reusedNames, logChanges + reusedNames, logChangesWhenResolvingModule ); } diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index e00a6751a70..c9b520c3777 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -171,7 +171,7 @@ namespace ts { afterProgramCreate(host: DirectoryStructureHost, program: Program): void; /** Optional module name resolver */ - moduleNameResolver?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } /** @@ -310,9 +310,6 @@ namespace ts { const getCachedDirectoryStructureHost = configFileName && (() => directoryStructureHost as CachedDirectoryStructureHost); const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); let newLine = getNewLineCharacter(compilerOptions, system); - const resolveModuleNames: (moduleNames: string[], containingFile: string, reusedNames?: string[]) => ResolvedModule[] = host.moduleNameResolver ? - (moduleNames, containingFile, reusedNames) => host.moduleNameResolver(moduleNames, containingFile, reusedNames) : - (moduleNames, containingFile, reusedNames?) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, /*logChanges*/ false); const compilerHost: CompilerHost & ResolutionCacheHost = { // Members for CompilerHost @@ -332,8 +329,6 @@ namespace ts { getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", getDirectories: path => directoryStructureHost.getDirectories(path), realpath, - resolveTypeReferenceDirectives: (typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile), - resolveModuleNames, onReleaseOldSourceFile, // Members for ResolutionCacheHost toPath, @@ -351,8 +346,14 @@ namespace ts { // Cache for the module resolution const resolutionCache = createResolutionCache(compilerHost, configFileName ? getDirectoryPath(getNormalizedAbsolutePath(configFileName, getCurrentDirectory())) : - getCurrentDirectory() + getCurrentDirectory(), + /*logChangesWhenResolvingModule*/ false ); + // Resolve module using host module resolution strategy if provided otherwise use resolution cache to resolve module names + compilerHost.resolveModuleNames = host.resolveModuleNames ? + host.resolveModuleNames.bind(host) : + resolutionCache.resolveModuleNames.bind(resolutionCache); + compilerHost.resolveTypeReferenceDirectives = resolutionCache.resolveTypeReferenceDirectives.bind(resolutionCache); synchronizeProgram(); diff --git a/src/server/project.ts b/src/server/project.ts index 9b9dbb99436..800e93f1949 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -237,7 +237,7 @@ namespace ts.server { } // Use the current directory as resolution root only if the project created using current directory string - this.resolutionCache = createResolutionCache(this, currentDirectory && this.currentDirectory); + this.resolutionCache = createResolutionCache(this, currentDirectory && this.currentDirectory, /*logChangesWhenResolvingModule*/ true); this.languageService = createLanguageService(this, this.documentRegistry); if (!languageServiceEnabled) { this.disableLanguageService(); @@ -353,7 +353,7 @@ namespace ts.server { } resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModuleFull[] { - return this.resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, /*logChanges*/ true); + return this.resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames); } resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] { diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index df02d53209c..c82599c18f2 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3828,7 +3828,7 @@ declare namespace ts { /** Custom action after new program creation is successful */ afterProgramCreate(host: DirectoryStructureHost, program: Program): void; /** Optional module name resolver */ - moduleNameResolver?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } /** * Host to create watch with root files and options From 71b7429cb26237e97748c839fa7ded0f422b47d9 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 4 Dec 2017 15:40:09 -0800 Subject: [PATCH 020/172] Update baselines After merging with master, even erroneous tests generate types and symbols baselines --- .../keyofAndIndexedAccessErrors.symbols | 31 ++++ .../keyofAndIndexedAccessErrors.types | 34 ++++ ...imitiveConstraintOfIndexAccessType.symbols | 145 ++++++++++++++++ ...PrimitiveConstraintOfIndexAccessType.types | 156 ++++++++++++++++++ 4 files changed, 366 insertions(+) create mode 100644 tests/baselines/reference/nonPrimitiveConstraintOfIndexAccessType.symbols create mode 100644 tests/baselines/reference/nonPrimitiveConstraintOfIndexAccessType.types diff --git a/tests/baselines/reference/keyofAndIndexedAccessErrors.symbols b/tests/baselines/reference/keyofAndIndexedAccessErrors.symbols index 36fb8d2989b..0e97ef793fb 100644 --- a/tests/baselines/reference/keyofAndIndexedAccessErrors.symbols +++ b/tests/baselines/reference/keyofAndIndexedAccessErrors.symbols @@ -268,3 +268,34 @@ function f20(k1: keyof (T | U), k2: keyof (T & U), o1: T | U, o2: T & U) { >k2 : Symbol(k2, Decl(keyofAndIndexedAccessErrors.ts, 69, 37)) >k1 : Symbol(k1, Decl(keyofAndIndexedAccessErrors.ts, 69, 19)) } + +// Repro from #17166 +function f3(obj: T, k: K, value: T[K]): void { +>f3 : Symbol(f3, Decl(keyofAndIndexedAccessErrors.ts, 78, 1)) +>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12)) +>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14)) +>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12)) +>obj : Symbol(obj, Decl(keyofAndIndexedAccessErrors.ts, 81, 34)) +>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12)) +>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 81, 41)) +>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14)) +>value : Symbol(value, Decl(keyofAndIndexedAccessErrors.ts, 81, 47)) +>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12)) +>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14)) + + for (let key in obj) { +>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 82, 12)) +>obj : Symbol(obj, Decl(keyofAndIndexedAccessErrors.ts, 81, 34)) + + k = key // error, keyof T =/=> K +>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 81, 41)) +>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 82, 12)) + + value = obj[key]; // error, T[keyof T] =/=> T[K] +>value : Symbol(value, Decl(keyofAndIndexedAccessErrors.ts, 81, 47)) +>obj : Symbol(obj, Decl(keyofAndIndexedAccessErrors.ts, 81, 34)) +>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 82, 12)) + } +} + + diff --git a/tests/baselines/reference/keyofAndIndexedAccessErrors.types b/tests/baselines/reference/keyofAndIndexedAccessErrors.types index 4d2e25641d3..f5cc7c093d9 100644 --- a/tests/baselines/reference/keyofAndIndexedAccessErrors.types +++ b/tests/baselines/reference/keyofAndIndexedAccessErrors.types @@ -299,3 +299,37 @@ function f20(k1: keyof (T | U), k2: keyof (T & U), o1: T | U, o2: T & U) { >k2 : keyof (T & U) >k1 : keyof (T | U) } + +// Repro from #17166 +function f3(obj: T, k: K, value: T[K]): void { +>f3 : (obj: T, k: K, value: T[K]) => void +>T : T +>K : K +>T : T +>obj : T +>T : T +>k : K +>K : K +>value : T[K] +>T : T +>K : K + + for (let key in obj) { +>key : keyof T +>obj : T + + k = key // error, keyof T =/=> K +>k = key : keyof T +>k : K +>key : keyof T + + value = obj[key]; // error, T[keyof T] =/=> T[K] +>value = obj[key] : T[keyof T] +>value : T[K] +>obj[key] : T[keyof T] +>obj : T +>key : keyof T + } +} + + diff --git a/tests/baselines/reference/nonPrimitiveConstraintOfIndexAccessType.symbols b/tests/baselines/reference/nonPrimitiveConstraintOfIndexAccessType.symbols new file mode 100644 index 00000000000..3ada1e3f3d1 --- /dev/null +++ b/tests/baselines/reference/nonPrimitiveConstraintOfIndexAccessType.symbols @@ -0,0 +1,145 @@ +=== tests/cases/conformance/types/nonPrimitive/nonPrimitiveConstraintOfIndexAccessType.ts === +// test for #15371 +function f(s: string, tp: T[P]): void { +>f : Symbol(f, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 0, 0)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 1, 11)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 1, 28)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 1, 11)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 1, 48)) +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 1, 58)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 1, 11)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 1, 28)) + + tp = s; +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 1, 58)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 1, 48)) +} +function g(s: string, tp: T[P]): void { +>g : Symbol(g, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 3, 1)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 4, 11)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 4, 26)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 4, 11)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 4, 46)) +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 4, 56)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 4, 11)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 4, 26)) + + tp = s; +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 4, 56)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 4, 46)) +} +function h(s: string, tp: T[P]): void { +>h : Symbol(h, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 6, 1)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 7, 11)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 7, 31)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 7, 11)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 7, 51)) +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 7, 61)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 7, 11)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 7, 31)) + + tp = s; +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 7, 61)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 7, 51)) +} +function i(s: string, tp: T[P]): void { +>i : Symbol(i, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 9, 1)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 10, 11)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 10, 26)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 10, 11)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 10, 46)) +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 10, 56)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 10, 11)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 10, 26)) + + tp = s; +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 10, 56)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 10, 46)) +} +function j(s: string, tp: T[P]): void { +>j : Symbol(j, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 12, 1)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 13, 11)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 13, 27)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 13, 11)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 13, 47)) +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 13, 57)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 13, 11)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 13, 27)) + + tp = s; +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 13, 57)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 13, 47)) +} +function k(s: string, tp: T[P]): void { +>k : Symbol(k, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 15, 1)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 16, 11)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 16, 28)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 16, 11)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 16, 48)) +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 16, 58)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 16, 11)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 16, 28)) + + tp = s; +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 16, 58)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 16, 48)) +} +function o(s: string, tp: T[P]): void { +>o : Symbol(o, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 18, 1)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 19, 11)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 19, 28)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 19, 11)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 19, 48)) +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 19, 58)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 19, 11)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 19, 28)) + + tp = s; +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 19, 58)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 19, 48)) +} +function l(s: string, tp: T[P]): void { +>l : Symbol(l, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 21, 1)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 22, 11)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 22, 24)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 22, 11)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 22, 44)) +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 22, 54)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 22, 11)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 22, 24)) + + tp = s; +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 22, 54)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 22, 44)) +} +function m(s: string, tp: T[P]): void { +>m : Symbol(m, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 24, 1)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 25, 11)) +>a : Symbol(a, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 25, 22)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 25, 35)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 25, 11)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 25, 55)) +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 25, 65)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 25, 11)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 25, 35)) + + tp = s; +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 25, 65)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 25, 55)) +} +function n(s: string, tp: T[P]): void { +>n : Symbol(n, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 27, 1)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 28, 11)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 28, 24)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 28, 45)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 28, 11)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 28, 65)) +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 28, 75)) +>T : Symbol(T, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 28, 11)) +>P : Symbol(P, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 28, 45)) + + tp = s; +>tp : Symbol(tp, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 28, 75)) +>s : Symbol(s, Decl(nonPrimitiveConstraintOfIndexAccessType.ts, 28, 65)) +} + diff --git a/tests/baselines/reference/nonPrimitiveConstraintOfIndexAccessType.types b/tests/baselines/reference/nonPrimitiveConstraintOfIndexAccessType.types new file mode 100644 index 00000000000..d5e8560d7cf --- /dev/null +++ b/tests/baselines/reference/nonPrimitiveConstraintOfIndexAccessType.types @@ -0,0 +1,156 @@ +=== tests/cases/conformance/types/nonPrimitive/nonPrimitiveConstraintOfIndexAccessType.ts === +// test for #15371 +function f(s: string, tp: T[P]): void { +>f : (s: string, tp: T[P]) => void +>T : T +>P : P +>T : T +>s : string +>tp : T[P] +>T : T +>P : P + + tp = s; +>tp = s : string +>tp : T[P] +>s : string +} +function g(s: string, tp: T[P]): void { +>g : (s: string, tp: T[P]) => void +>T : T +>null : null +>P : P +>T : T +>s : string +>tp : T[P] +>T : T +>P : P + + tp = s; +>tp = s : string +>tp : T[P] +>s : string +} +function h(s: string, tp: T[P]): void { +>h : (s: string, tp: T[P]) => void +>T : T +>P : P +>T : T +>s : string +>tp : T[P] +>T : T +>P : P + + tp = s; +>tp = s : string +>tp : T[P] +>s : string +} +function i(s: string, tp: T[P]): void { +>i : (s: string, tp: T[P]) => void +>T : T +>P : P +>T : T +>s : string +>tp : T[P] +>T : T +>P : P + + tp = s; +>tp = s : string +>tp : T[P] +>s : string +} +function j(s: string, tp: T[P]): void { +>j : (s: string, tp: T[P]) => void +>T : T +>P : P +>T : T +>s : string +>tp : T[P] +>T : T +>P : P + + tp = s; +>tp = s : string +>tp : T[P] +>s : string +} +function k(s: string, tp: T[P]): void { +>k : (s: string, tp: T[P]) => void +>T : T +>P : P +>T : T +>s : string +>tp : T[P] +>T : T +>P : P + + tp = s; +>tp = s : string +>tp : T[P] +>s : string +} +function o(s: string, tp: T[P]): void { +>o : (s: string, tp: T[P]) => void +>T : T +>P : P +>T : T +>s : string +>tp : T[P] +>T : T +>P : P + + tp = s; +>tp = s : string +>tp : T[P] +>s : string +} +function l(s: string, tp: T[P]): void { +>l : (s: string, tp: T[P]) => void +>T : T +>P : P +>T : T +>s : string +>tp : T[P] +>T : T +>P : P + + tp = s; +>tp = s : string +>tp : T[P] +>s : string +} +function m(s: string, tp: T[P]): void { +>m : (s: string, tp: T[P]) => void +>T : T +>a : number +>P : P +>T : T +>s : string +>tp : T[P] +>T : T +>P : P + + tp = s; +>tp = s : string +>tp : T[P] +>s : string +} +function n(s: string, tp: T[P]): void { +>n : (s: string, tp: T[P]) => void +>T : T +>s : string +>P : P +>T : T +>s : string +>tp : T[P] +>T : T +>P : P + + tp = s; +>tp = s : string +>tp : T[P] +>s : string +} + From 1a91256c2276972f5393e917b4cf26f7ac855578 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 4 Dec 2017 15:24:22 -0800 Subject: [PATCH 021/172] Make before and after program create callbacks optional --- src/compiler/watch.ts | 14 +++++++------- tests/baselines/reference/api/typescript.d.ts | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index c9b520c3777..5e5a97e16a2 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -165,10 +165,10 @@ namespace ts { /** FS system to use */ system: System; - /** Custom action before creating the program */ - beforeProgramCreate(compilerOptions: CompilerOptions): void; - /** Custom action after new program creation is successful */ - afterProgramCreate(host: DirectoryStructureHost, program: Program): void; + /** If provided, callback to invoke before each program creation */ + beforeProgramCreate?(compilerOptions: CompilerOptions): void; + /** If provided, callback to invoke after every new program creation */ + afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; /** Optional module name resolver */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; @@ -239,7 +239,6 @@ namespace ts { export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { return createWatch({ system, - beforeProgramCreate: noop, afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic), onConfigFileDiagnostic: reportDiagnostic || createDiagnosticReporter(system), configFileName, @@ -253,7 +252,6 @@ namespace ts { export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfFilesAndCompilerOptions { return createWatch({ system, - beforeProgramCreate: noop, afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic), rootFiles, options @@ -286,7 +284,9 @@ namespace ts { let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed - const { system, configFileName, onConfigFileDiagnostic, afterProgramCreate, beforeProgramCreate, optionsToExtend: optionsToExtendForConfigFile = {} } = host as WatchOfConfigFileHost; + const { system, configFileName, onConfigFileDiagnostic, optionsToExtend: optionsToExtendForConfigFile = {} } = host as WatchOfConfigFileHost; + const beforeProgramCreate: WatchHost["beforeProgramCreate"] = host.beforeProgramCreate ? host.beforeProgramCreate.bind(host) : noop; + const afterProgramCreate: WatchHost["afterProgramCreate"] = host.afterProgramCreate ? host.afterProgramCreate.bind(host) : noop; let { rootFiles: rootFileNames, options: compilerOptions, configFileSpecs, configFileWildCardDirectories } = host as WatchOfConfigFileHost; // From tsc we want to get already parsed result and hence check for rootFileNames diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index c82599c18f2..13b5b17f50f 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3823,10 +3823,10 @@ declare namespace ts { interface WatchHost { /** FS system to use */ system: System; - /** Custom action before creating the program */ - beforeProgramCreate(compilerOptions: CompilerOptions): void; - /** Custom action after new program creation is successful */ - afterProgramCreate(host: DirectoryStructureHost, program: Program): void; + /** If provided, callback to invoke before each program creation */ + beforeProgramCreate?(compilerOptions: CompilerOptions): void; + /** If provided, callback to invoke after every new program creation */ + afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; /** Optional module name resolver */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } From 944f8b879288581d04e0f69ee649a2361547b700 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 4 Dec 2017 18:17:25 -0800 Subject: [PATCH 022/172] Instead of using system as object on WatchHost, create WatchCompilerHost that combines the functionality --- src/compiler/core.ts | 2 - src/compiler/sys.ts | 4 +- src/compiler/tsc.ts | 41 +++++----- src/compiler/utilities.ts | 4 +- src/compiler/watch.ts | 82 +++++++++++-------- src/server/project.ts | 2 +- src/services/services.ts | 2 +- .../reference/api/tsserverlibrary.d.ts | 4 +- tests/baselines/reference/api/typescript.d.ts | 18 ++-- 9 files changed, 87 insertions(+), 72 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 48c1b84dbaa..e1443215c91 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -3023,9 +3023,7 @@ namespace ts { const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); return { useCaseSensitiveFileNames: host.useCaseSensitiveFileNames, - newLine: host.newLine, readFile: (path, encoding) => host.readFile(path, encoding), - write: s => host.write(s), writeFile, fileExists, directoryExists, diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 841dba8a528..c327bd608de 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -34,9 +34,7 @@ namespace ts { * Partial interface of the System thats needed to support the caching of directory structure */ export interface DirectoryStructureHost { - newLine: string; useCaseSensitiveFileNames: boolean; - write(s: string): void; readFile(path: string, encoding?: string): string | undefined; writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; fileExists(path: string): boolean; @@ -49,7 +47,9 @@ namespace ts { } export interface System extends DirectoryStructureHost { + newLine: string; args: string[]; + write(s: string): void; getFileSize?(path: string): number; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 5342319e75d..9a7e98d954c 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -150,37 +150,34 @@ namespace ts { return sys.exit(exitStatus); } - function createProgramCompilerWithBuilderState() { - const compilerWithBuilderState = ts.createProgramCompilerWithBuilderState(sys, reportDiagnostic); - return (host: DirectoryStructureHost, program: Program) => { + function createWatchCompilerHost(): WatchCompilerHost { + const watchCompilerHost = ts.createWatchCompilerHost(sys, reportDiagnostic); + const compilerWithBuilderState = watchCompilerHost.afterProgramCreate; + watchCompilerHost.beforeProgramCreate = enableStatistics; + watchCompilerHost.afterProgramCreate = (host, program) => { compilerWithBuilderState(host, program); reportStatistics(program); }; + return watchCompilerHost; } function createWatchOfConfigFile(configParseResult: ParsedCommandLine, optionsToExtend: CompilerOptions) { - createWatch({ - system: sys, - beforeProgramCreate: enableStatistics, - afterProgramCreate: createProgramCompilerWithBuilderState(), - onConfigFileDiagnostic: reportDiagnostic, - rootFiles: configParseResult.fileNames, - options: configParseResult.options, - configFileName: configParseResult.options.configFilePath, - optionsToExtend, - configFileSpecs: configParseResult.configFileSpecs, - configFileWildCardDirectories: configParseResult.wildcardDirectories - }); + const watchCompilerHost = createWatchCompilerHost() as WatchCompilerHostOfConfigFile; + watchCompilerHost.onConfigFileDiagnostic = reportDiagnostic; + watchCompilerHost.rootFiles = configParseResult.fileNames; + watchCompilerHost.options = configParseResult.options; + watchCompilerHost.configFileName = configParseResult.options.configFilePath; + watchCompilerHost.optionsToExtend = optionsToExtend; + watchCompilerHost.configFileSpecs = configParseResult.configFileSpecs; + watchCompilerHost.configFileWildCardDirectories = configParseResult.wildcardDirectories; + createWatch(watchCompilerHost); } function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions) { - createWatch({ - system: sys, - beforeProgramCreate: enableStatistics, - afterProgramCreate: createProgramCompilerWithBuilderState(), - rootFiles, - options - }); + const watchCompilerHost = createWatchCompilerHost() as WatchCompilerHostOfFilesAndCompilerOptions; + watchCompilerHost.rootFiles = rootFiles; + watchCompilerHost.options = options; + createWatch(watchCompilerHost); } function compileProgram(program: Program): ExitStatus { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 0e62c2d8cea..6cfe8c03e9a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3306,14 +3306,14 @@ namespace ts { const carriageReturnLineFeed = "\r\n"; const lineFeed = "\n"; - export function getNewLineCharacter(options: CompilerOptions | PrinterOptions, system?: { newLine: string }): string { + export function getNewLineCharacter(options: CompilerOptions | PrinterOptions, getNewLine?: () => string): string { switch (options.newLine) { case NewLineKind.CarriageReturnLineFeed: return carriageReturnLineFeed; case NewLineKind.LineFeed: return lineFeed; } - return system ? system.newLine : sys ? sys.newLine : carriageReturnLineFeed; + return getNewLine ? getNewLine() : sys ? sys.newLine : carriageReturnLineFeed; } /** diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index c2ab3aae591..b5128be552e 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -161,7 +161,7 @@ namespace ts { }; } - export interface WatchHost { + export interface WatchCompilerHost { /** FS system to use */ system: System; @@ -170,14 +170,18 @@ namespace ts { /** If provided, callback to invoke after every new program creation */ afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; - /** Optional module name resolver */ + // Sub set of compiler host methods to read and generate new program + useCaseSensitiveFileNames(): boolean; + getNewLine(): string; + + /** If provided this function would be used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } /** * Host to create watch with root files and options */ - export interface WatchOfFilesAndCompilerOptionsHost extends WatchHost { + export interface WatchCompilerHostOfFilesAndCompilerOptions extends WatchCompilerHost { /** root files to use to generate program */ rootFiles: string[]; @@ -188,7 +192,7 @@ namespace ts { /** * Host to create watch with config file */ - export interface WatchOfConfigFileHost extends WatchHost { + export interface WatchCompilerHostOfConfigFile extends WatchCompilerHost { /** Name of the config file to compile */ configFileName: string; @@ -199,11 +203,11 @@ namespace ts { onConfigFileDiagnostic(diagnostic: Diagnostic): void; } - /*@internal*/ /** * Host to create watch with config file that is already parsed (from tsc) */ - export interface WatchOfConfigFileHost extends WatchHost { + /*@internal*/ + export interface WatchCompilerHostOfConfigFile extends WatchCompilerHost { rootFiles?: string[]; options?: CompilerOptions; optionsToExtend?: CompilerOptions; @@ -233,40 +237,49 @@ namespace ts { updateRootFileNames(fileNames: string[]): void; } + /** + * Creates the watch compiler host that can be extended with config file or root file names and options host + */ + /*@internal*/ + export function createWatchCompilerHost(system = sys, reportDiagnostic?: DiagnosticReporter): WatchCompilerHost { + return { + useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, + getNewLine: () => system.newLine, + system, + afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) + }; + } + /** * Create the watched program for config file */ - export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { - return createWatch({ - system, - afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic), - onConfigFileDiagnostic: reportDiagnostic || createDiagnosticReporter(system), - configFileName, - optionsToExtend - }); + export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { + const host = createWatchCompilerHost(system) as WatchCompilerHostOfConfigFile; + host.onConfigFileDiagnostic = reportDiagnostic || createDiagnosticReporter(system); + host.configFileName = configFileName; + host.optionsToExtend = optionsToExtend; + return createWatch(host); } /** * Create the watched program for root files and compiler options */ export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfFilesAndCompilerOptions { - return createWatch({ - system, - afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic), - rootFiles, - options - }); + const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfFilesAndCompilerOptions; + host.rootFiles = rootFiles; + host.options = options; + return createWatch(host); } /** * Creates the watch from the host for root files and compiler options */ - export function createWatch(host: WatchOfFilesAndCompilerOptionsHost): WatchOfFilesAndCompilerOptions; + export function createWatch(host: WatchCompilerHostOfFilesAndCompilerOptions): WatchOfFilesAndCompilerOptions; /** * Creates the watch from the host for config file */ - export function createWatch(host: WatchOfConfigFileHost): WatchOfConfigFile; - export function createWatch(host: WatchOfFilesAndCompilerOptionsHost | WatchOfConfigFileHost): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { + export function createWatch(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; + export function createWatch(host: WatchCompilerHostOfFilesAndCompilerOptions | WatchCompilerHostOfConfigFile): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { interface HostFileInfo { version: number; sourceFile: SourceFile; @@ -284,10 +297,10 @@ namespace ts { let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed - const { system, configFileName, onConfigFileDiagnostic, optionsToExtend: optionsToExtendForConfigFile = {} } = host as WatchOfConfigFileHost; - const beforeProgramCreate: WatchHost["beforeProgramCreate"] = host.beforeProgramCreate ? host.beforeProgramCreate.bind(host) : noop; - const afterProgramCreate: WatchHost["afterProgramCreate"] = host.afterProgramCreate ? host.afterProgramCreate.bind(host) : noop; - let { rootFiles: rootFileNames, options: compilerOptions, configFileSpecs, configFileWildCardDirectories } = host as WatchOfConfigFileHost; + const { system, configFileName, onConfigFileDiagnostic, optionsToExtend: optionsToExtendForConfigFile = {} } = host as WatchCompilerHostOfConfigFile; + const beforeProgramCreate: WatchCompilerHost["beforeProgramCreate"] = host.beforeProgramCreate ? host.beforeProgramCreate.bind(host) : noop; + const afterProgramCreate: WatchCompilerHost["afterProgramCreate"] = host.afterProgramCreate ? host.afterProgramCreate.bind(host) : noop; + let { rootFiles: rootFileNames, options: compilerOptions, configFileSpecs, configFileWildCardDirectories } = host as WatchCompilerHostOfConfigFile; // From tsc we want to get already parsed result and hence check for rootFileNames const directoryStructureHost = configFileName ? createCachedDirectoryStructureHost(system) : system; @@ -296,7 +309,7 @@ namespace ts { } const loggingEnabled = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; - const writeLog: (s: string) => void = loggingEnabled ? s => { system.write(s); system.write(system.newLine); } : noop; + const writeLog: (s: string) => void = loggingEnabled ? s => { system.write(s); system.write(newLine); } : noop; const watchFile = compilerOptions.extendedDiagnostics ? ts.addFileWatcherWithLogging : loggingEnabled ? ts.addFileWatcherWithOnlyTriggerLogging : ts.addFileWatcher; const watchFilePath = compilerOptions.extendedDiagnostics ? ts.addFilePathWatcherWithLogging : ts.addFilePathWatcher; const watchDirectoryWorker = compilerOptions.extendedDiagnostics ? ts.addDirectoryWatcherWithLogging : ts.addDirectoryWatcher; @@ -308,8 +321,9 @@ namespace ts { const getCurrentDirectory = memoize(() => directoryStructureHost.getCurrentDirectory()); const realpath = system.realpath && ((path: string) => system.realpath(path)); const getCachedDirectoryStructureHost = configFileName && (() => directoryStructureHost as CachedDirectoryStructureHost); - const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); - let newLine = getNewLineCharacter(compilerOptions, system); + const useCaseSensitiveFileNames = memoize(() => host.useCaseSensitiveFileNames()); + const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames()); + let newLine = updateNewLine(); const compilerHost: CompilerHost & ResolutionCacheHost = { // Members for CompilerHost @@ -319,7 +333,7 @@ namespace ts { getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), writeFile: notImplemented, getCurrentDirectory, - useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, + useCaseSensitiveFileNames, getCanonicalFileName, getNewLine: () => newLine, fileExists, @@ -370,7 +384,7 @@ namespace ts { writeLog(`Synchronizing program`); if (hasChangedCompilerOptions) { - newLine = getNewLineCharacter(compilerOptions, system); + newLine = updateNewLine(); if (program && changesAffectModuleResolution(program.getCompilerOptions(), compilerOptions)) { resolutionCache.clear(); } @@ -423,6 +437,10 @@ namespace ts { scheduleProgramUpdate(); } + function updateNewLine() { + return getNewLineCharacter(compilerOptions, () => host.getNewLine()); + } + function toPath(fileName: string) { return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName); } diff --git a/src/server/project.ts b/src/server/project.ts index 800e93f1949..40ddaeb2a19 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -266,7 +266,7 @@ namespace ts.server { } getNewLine() { - return this.directoryStructureHost.newLine; + return this.projectService.host.newLine; } getProjectVersion() { diff --git a/src/services/services.ts b/src/services/services.ts index 3c39affcacb..b9a50758326 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1255,7 +1255,7 @@ namespace ts { getCancellationToken: () => cancellationToken, getCanonicalFileName, useCaseSensitiveFileNames: () => useCaseSensitivefileNames, - getNewLine: () => getNewLineCharacter(newSettings, { newLine: getNewLineOrDefaultFromHost(host) }), + getNewLine: () => getNewLineCharacter(newSettings, () => getNewLineOrDefaultFromHost(host)), getDefaultLibFileName: (options) => host.getDefaultLibFileName(options), writeFile: noop, getCurrentDirectory: () => currentDirectory, diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index eb9a788ac7f..f0f9318e687 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2729,9 +2729,7 @@ declare namespace ts { * Partial interface of the System thats needed to support the caching of directory structure */ interface DirectoryStructureHost { - newLine: string; useCaseSensitiveFileNames: boolean; - write(s: string): void; readFile(path: string, encoding?: string): string | undefined; writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; fileExists(path: string): boolean; @@ -2743,7 +2741,9 @@ declare namespace ts { exit(exitCode?: number): void; } interface System extends DirectoryStructureHost { + newLine: string; args: string[]; + write(s: string): void; getFileSize?(path: string): number; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 13b5b17f50f..5026237f30e 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2729,9 +2729,7 @@ declare namespace ts { * Partial interface of the System thats needed to support the caching of directory structure */ interface DirectoryStructureHost { - newLine: string; useCaseSensitiveFileNames: boolean; - write(s: string): void; readFile(path: string, encoding?: string): string | undefined; writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; fileExists(path: string): boolean; @@ -2743,7 +2741,9 @@ declare namespace ts { exit(exitCode?: number): void; } interface System extends DirectoryStructureHost { + newLine: string; args: string[]; + write(s: string): void; getFileSize?(path: string): number; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that @@ -3820,20 +3820,22 @@ declare namespace ts { * Creates the function that compiles the program by maintaining the builder for the program and reports the errors and emits files */ function createProgramCompilerWithBuilderState(system?: System, reportDiagnostic?: DiagnosticReporter): (host: DirectoryStructureHost, program: Program) => void; - interface WatchHost { + interface WatchCompilerHost { /** FS system to use */ system: System; /** If provided, callback to invoke before each program creation */ beforeProgramCreate?(compilerOptions: CompilerOptions): void; /** If provided, callback to invoke after every new program creation */ afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; - /** Optional module name resolver */ + useCaseSensitiveFileNames(): boolean; + getNewLine(): string; + /** If provided this function would be used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } /** * Host to create watch with root files and options */ - interface WatchOfFilesAndCompilerOptionsHost extends WatchHost { + interface WatchCompilerHostOfFilesAndCompilerOptions extends WatchCompilerHost { /** root files to use to generate program */ rootFiles: string[]; /** Compiler options */ @@ -3842,7 +3844,7 @@ declare namespace ts { /** * Host to create watch with config file */ - interface WatchOfConfigFileHost extends WatchHost { + interface WatchCompilerHostOfConfigFile extends WatchCompilerHost { /** Name of the config file to compile */ configFileName: string; /** Options to extend */ @@ -3876,11 +3878,11 @@ declare namespace ts { /** * Creates the watch from the host for root files and compiler options */ - function createWatch(host: WatchOfFilesAndCompilerOptionsHost): WatchOfFilesAndCompilerOptions; + function createWatch(host: WatchCompilerHostOfFilesAndCompilerOptions): WatchOfFilesAndCompilerOptions; /** * Creates the watch from the host for config file */ - function createWatch(host: WatchOfConfigFileHost): WatchOfConfigFile; + function createWatch(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; } declare namespace ts { function parseCommandLine(commandLine: ReadonlyArray, readFile?: (path: string) => string | undefined): ParsedCommandLine; From 43c2610a69748a875b08ca3a3c9e3e9cdece3b83 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Dec 2017 16:34:17 -0800 Subject: [PATCH 023/172] More functions moved from system to WatchCompilerHost --- src/compiler/core.ts | 219 +--------------- src/compiler/program.ts | 1 - src/compiler/resolutionCache.ts | 11 +- src/compiler/sys.ts | 30 +-- src/compiler/tsc.ts | 2 +- src/compiler/types.ts | 1 - src/compiler/watch.ts | 187 ++++++++++---- src/compiler/watchUtilities.ts | 241 ++++++++++++++++++ .../unittests/reuseProgramStructure.ts | 2 +- src/server/editorServices.ts | 2 +- src/server/project.ts | 11 +- src/server/scriptInfo.ts | 2 +- .../reference/api/tsserverlibrary.d.ts | 30 +-- tests/baselines/reference/api/typescript.d.ts | 72 ++++-- 14 files changed, 478 insertions(+), 333 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index e1443215c91..b6472987398 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1398,6 +1398,9 @@ namespace ts { /** Returns its argument. */ export function identity(x: T) { return x; } + /** Returns lower case string */ + export function toLowerCase(x: string) { return x.toLowerCase(); } + /** Throws an error because a function is not implemented. */ export function notImplemented(): never { throw new Error("Not implemented"); @@ -2877,9 +2880,7 @@ namespace ts { export type GetCanonicalFileName = (fileName: string) => string; export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean): GetCanonicalFileName { - return useCaseSensitiveFileNames - ? ((fileName) => fileName) - : ((fileName) => fileName.toLowerCase()); + return useCaseSensitiveFileNames ? identity : toLowerCase; } /** @@ -3000,215 +3001,7 @@ namespace ts { export function assertTypeIsNever(_: never): void { } // tslint:disable-line no-empty - export interface FileAndDirectoryExistence { - fileExists: boolean; - directoryExists: boolean; - } - - export interface CachedDirectoryStructureHost extends DirectoryStructureHost { - /** Returns the queried result for the file exists and directory exists if at all it was done */ - addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path): FileAndDirectoryExistence | undefined; - addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void; - clearCache(): void; - } - - interface MutableFileSystemEntries { - readonly files: string[]; - readonly directories: string[]; - } - - export function createCachedDirectoryStructureHost(host: DirectoryStructureHost): CachedDirectoryStructureHost { - const cachedReadDirectoryResult = createMap(); - const getCurrentDirectory = memoize(() => host.getCurrentDirectory()); - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); - return { - useCaseSensitiveFileNames: host.useCaseSensitiveFileNames, - readFile: (path, encoding) => host.readFile(path, encoding), - writeFile, - fileExists, - directoryExists, - createDirectory, - getCurrentDirectory, - getDirectories, - readDirectory, - addOrDeleteFileOrDirectory, - addOrDeleteFile, - clearCache, - exit: code => host.exit(code) - }; - - function toPath(fileName: string) { - return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName); - } - - function getCachedFileSystemEntries(rootDirPath: Path): MutableFileSystemEntries | undefined { - return cachedReadDirectoryResult.get(rootDirPath); - } - - function getCachedFileSystemEntriesForBaseDir(path: Path): MutableFileSystemEntries | undefined { - return getCachedFileSystemEntries(getDirectoryPath(path)); - } - - function getBaseNameOfFileName(fileName: string) { - return getBaseFileName(normalizePath(fileName)); - } - - function createCachedFileSystemEntries(rootDir: string, rootDirPath: Path) { - const resultFromHost: MutableFileSystemEntries = { - files: map(host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]), getBaseNameOfFileName) || [], - directories: host.getDirectories(rootDir) || [] - }; - - cachedReadDirectoryResult.set(rootDirPath, resultFromHost); - return resultFromHost; - } - - /** - * If the readDirectory result was already cached, it returns that - * Otherwise gets result from host and caches it. - * The host request is done under try catch block to avoid caching incorrect result - */ - function tryReadDirectory(rootDir: string, rootDirPath: Path): MutableFileSystemEntries | undefined { - const cachedResult = getCachedFileSystemEntries(rootDirPath); - if (cachedResult) { - return cachedResult; - } - - try { - return createCachedFileSystemEntries(rootDir, rootDirPath); - } - catch (_e) { - // If there is exception to read directories, dont cache the result and direct the calls to host - Debug.assert(!cachedReadDirectoryResult.has(rootDirPath)); - return undefined; - } - } - - function fileNameEqual(name1: string, name2: string) { - return getCanonicalFileName(name1) === getCanonicalFileName(name2); - } - - function hasEntry(entries: ReadonlyArray, name: string) { - return some(entries, file => fileNameEqual(file, name)); - } - - function updateFileSystemEntry(entries: string[], baseName: string, isValid: boolean) { - if (hasEntry(entries, baseName)) { - if (!isValid) { - return filterMutate(entries, entry => !fileNameEqual(entry, baseName)); - } - } - else if (isValid) { - return entries.push(baseName); - } - } - - function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { - const path = toPath(fileName); - const result = getCachedFileSystemEntriesForBaseDir(path); - if (result) { - updateFilesOfFileSystemEntry(result, getBaseNameOfFileName(fileName), /*fileExists*/ true); - } - return host.writeFile(fileName, data, writeByteOrderMark); - } - - function fileExists(fileName: string): boolean { - const path = toPath(fileName); - const result = getCachedFileSystemEntriesForBaseDir(path); - return result && hasEntry(result.files, getBaseNameOfFileName(fileName)) || - host.fileExists(fileName); - } - - function directoryExists(dirPath: string): boolean { - const path = toPath(dirPath); - return cachedReadDirectoryResult.has(path) || host.directoryExists(dirPath); - } - - function createDirectory(dirPath: string) { - const path = toPath(dirPath); - const result = getCachedFileSystemEntriesForBaseDir(path); - const baseFileName = getBaseNameOfFileName(dirPath); - if (result) { - updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true); - } - host.createDirectory(dirPath); - } - - function getDirectories(rootDir: string): string[] { - const rootDirPath = toPath(rootDir); - const result = tryReadDirectory(rootDir, rootDirPath); - if (result) { - return result.directories.slice(); - } - return host.getDirectories(rootDir); - } - - function readDirectory(rootDir: string, extensions?: ReadonlyArray, excludes?: ReadonlyArray, includes?: ReadonlyArray, depth?: number): string[] { - const rootDirPath = toPath(rootDir); - const result = tryReadDirectory(rootDir, rootDirPath); - if (result) { - return matchFiles(rootDir, extensions, excludes, includes, host.useCaseSensitiveFileNames, getCurrentDirectory(), depth, getFileSystemEntries); - } - return host.readDirectory(rootDir, extensions, excludes, includes, depth); - - function getFileSystemEntries(dir: string) { - const path = toPath(dir); - if (path === rootDirPath) { - return result; - } - return getCachedFileSystemEntries(path) || createCachedFileSystemEntries(dir, path); - } - } - - function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) { - const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath); - if (existingResult) { - // Just clear the cache for now - // For now just clear the cache, since this could mean that multiple level entries might need to be re-evaluated - clearCache(); - } - else { - // This was earlier a file (hence not in cached directory contents) - // or we never cached the directory containing it - const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrDirectoryPath); - if (parentResult) { - const baseName = getBaseNameOfFileName(fileOrDirectory); - if (parentResult) { - const fsQueryResult: FileAndDirectoryExistence = { - fileExists: host.fileExists(fileOrDirectoryPath), - directoryExists: host.directoryExists(fileOrDirectoryPath) - }; - if (fsQueryResult.directoryExists || hasEntry(parentResult.directories, baseName)) { - // Folder added or removed, clear the cache instead of updating the folder and its structure - clearCache(); - } - else { - // No need to update the directory structure, just files - updateFilesOfFileSystemEntry(parentResult, baseName, fsQueryResult.fileExists); - } - return fsQueryResult; - } - } - } - } - - function addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind) { - if (eventKind === FileWatcherEventKind.Changed) { - return; - } - - const parentResult = getCachedFileSystemEntriesForBaseDir(filePath); - if (parentResult) { - updateFilesOfFileSystemEntry(parentResult, getBaseNameOfFileName(fileName), eventKind === FileWatcherEventKind.Created); - } - } - - function updateFilesOfFileSystemEntry(parentResult: MutableFileSystemEntries, baseName: string, fileExists: boolean) { - updateFileSystemEntry(parentResult.files, baseName, fileExists); - } - - function clearCache() { - cachedReadDirectoryResult.clear(); - } + export function getBoundFunction(method: T | undefined, methodOf: {}): T | undefined { + return method && method.bind(methodOf); } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 9560859c7ce..9960b501d78 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -191,7 +191,6 @@ namespace ts { readFile: fileName => sys.readFile(fileName), trace: (s: string) => sys.write(s + newLine), directoryExists: directoryName => sys.directoryExists(directoryName), - getEnvironmentVariable: name => sys.getEnvironmentVariable ? sys.getEnvironmentVariable(name) : "", getDirectories: (path: string) => sys.getDirectories(path), realpath }; diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 1c96dc21f08..92eea4140da 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -47,7 +47,7 @@ namespace ts { onInvalidatedResolution(): void; watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; onChangedAutomaticTypeDirectiveNames(): void; - getCachedDirectoryStructureHost?(): CachedDirectoryStructureHost; + getCachedDirectoryStructureHost(): CachedDirectoryStructureHost | undefined; projectName?: string; getGlobalCache?(): string | undefined; writeLog(s: string): void; @@ -87,6 +87,7 @@ namespace ts { const perDirectoryResolvedTypeReferenceDirectives = createMap>(); const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory()); + const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost(); /** * These are the extensions that failed lookup files will have by default, @@ -467,9 +468,9 @@ namespace ts { function createDirectoryWatcher(directory: string, dirPath: Path) { return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => { const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); - if (resolutionHost.getCachedDirectoryStructureHost) { + if (cachedDirectoryStructureHost) { // Since the file existance changed, update the sourceFiles cache - resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); } // If the files are added to project root or node_modules directory, always run through the invalidation process @@ -596,9 +597,9 @@ namespace ts { // Create new watch and recursive info return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => { const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); - if (resolutionHost.getCachedDirectoryStructureHost) { + if (cachedDirectoryStructureHost) { // Since the file existance changed, update the sourceFiles cache - resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); } // For now just recompile diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index c327bd608de..f744b9ab0eb 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -30,27 +30,14 @@ namespace ts { mtime?: Date; } - /** - * Partial interface of the System thats needed to support the caching of directory structure - */ - export interface DirectoryStructureHost { - useCaseSensitiveFileNames: boolean; - readFile(path: string, encoding?: string): string | undefined; - writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; - fileExists(path: string): boolean; - directoryExists(path: string): boolean; - createDirectory(path: string): void; - getCurrentDirectory(): string; - getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; - exit(exitCode?: number): void; - } - - export interface System extends DirectoryStructureHost { - newLine: string; + export interface System { args: string[]; + newLine: string; + useCaseSensitiveFileNames: boolean; write(s: string): void; + readFile(path: string, encoding?: string): string | undefined; getFileSize?(path: string): number; + writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that * use native OS file watching @@ -58,7 +45,13 @@ namespace ts { watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; resolvePath(path: string): string; + fileExists(path: string): boolean; + directoryExists(path: string): boolean; + createDirectory(path: string): void; getExecutingFilePath(): string; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; getModifiedTime?(path: string): Date; /** * This should be cryptographically secure. @@ -66,6 +59,7 @@ namespace ts { */ createHash?(data: string): string; getMemoryUsage?(): number; + exit(exitCode?: number): void; realpath?(path: string): string; /*@internal*/ getEnvironmentVariable(name: string): string; /*@internal*/ tryEnableSourceMapsForHost?(): void; diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 9a7e98d954c..daccfca33b8 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -110,7 +110,7 @@ namespace ts { const commandLineOptions = commandLine.options; if (configFileName) { - const configParseResult = parseConfigFile(configFileName, commandLineOptions, sys, reportDiagnostic); + const configParseResult = parseConfigFileWithSystem(configFileName, commandLineOptions, sys, reportDiagnostic); udpateReportDiagnostic(configParseResult.options); if (isWatchSet(configParseResult.options)) { reportWatchModeWithoutSysSupport(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0167bd50116..4b3c15df706 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4327,7 +4327,6 @@ namespace ts { * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; - getEnvironmentVariable?(name: string): string; /* @internal */ onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions): void; /* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution; /* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean; diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index b5128be552e..0bd0fc499b7 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -33,34 +33,52 @@ namespace ts { }; } + /** + * Interface extending ParseConfigHost to support ParseConfigFile that reads config file and reports errors + */ + /*@internal*/ + export interface ParseConfigFileHost extends ParseConfigHost, ConfigFileDiagnosticsReporter { + getCurrentDirectory(): string; + } + + /** Parses config file using System interface */ + /*@internal*/ + export function parseConfigFileWithSystem(configFileName: string, optionsToExtend: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter) { + const host: ParseConfigFileHost = system; + host.onConfigFileDiagnostic = reportDiagnostic; + host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(sys, reportDiagnostic, diagnostic); + const result = parseConfigFile(configFileName, optionsToExtend, host); + host.onConfigFileDiagnostic = undefined; + host.onUnRecoverableConfigFileDiagnostic = undefined; + return result; + } + /** * Reads the config file, reports errors if any and exits if the config file cannot be found */ /*@internal*/ - export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: DirectoryStructureHost, reportDiagnostic: DiagnosticReporter): ParsedCommandLine { + export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost): ParsedCommandLine | undefined { let configFileText: string; try { - configFileText = system.readFile(configFileName); + configFileText = host.readFile(configFileName); } catch (e) { const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message); - reportDiagnostic(error); - system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - return; + host.onUnRecoverableConfigFileDiagnostic(error); + return undefined; } if (!configFileText) { const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); - reportDiagnostic(error); - system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - return; + host.onUnRecoverableConfigFileDiagnostic(error); + return undefined; } const result = parseJsonText(configFileName, configFileText); - result.parseDiagnostics.forEach(reportDiagnostic); + result.parseDiagnostics.forEach(diagnostic => host.onConfigFileDiagnostic(diagnostic)); - const cwd = system.getCurrentDirectory(); - const configParseResult = parseJsonSourceFileConfigFileContent(result, system, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd)); - configParseResult.errors.forEach(reportDiagnostic); + const cwd = host.getCurrentDirectory(); + const configParseResult = parseJsonSourceFileConfigFileContent(result, host, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd)); + configParseResult.errors.forEach(diagnostic => host.onConfigFileDiagnostic(diagnostic)); return configParseResult; } @@ -90,7 +108,7 @@ namespace ts { reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); const builder = createEmitAndSemanticDiagnosticsBuilder({ getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), - computeHash: system.createHash ? system.createHash.bind(system) : identity + computeHash: getBoundFunction(system.createHash, system) || identity }); return (host: DirectoryStructureHost, program: Program) => { @@ -173,8 +191,30 @@ namespace ts { // Sub set of compiler host methods to read and generate new program useCaseSensitiveFileNames(): boolean; getNewLine(): string; + getCurrentDirectory(): string; - /** If provided this function would be used to resolve the module names, otherwise typescript's default module resolution */ + /** + * Use to check file presence for source files and + * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well + */ + fileExists(path: string): boolean; + /** + * Use to read file text for source files and + * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well + */ + readFile(path: string, encoding?: string): string | undefined; + + /** If provided, used for module resolution as well as to handle directory structure */ + directoryExists?(path: string): boolean; + /** If provided, used in resolutions as well as handling directory structure */ + getDirectories?(path: string): string[]; + /** If provided, used to cache and handle directory structure modifications */ + readDirectory?(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + + /** Symbol links resolution */ + realpath?(path: string): string; + + /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } @@ -189,18 +229,36 @@ namespace ts { options: CompilerOptions; } + /** + * Reports config file diagnostics + */ + export interface ConfigFileDiagnosticsReporter { + /** + * Reports the diagnostics in reading/writing or parsing of the config file + */ + onConfigFileDiagnostic(diagnostic: Diagnostic): void; + + /** + * Reports unrecoverable error when parsing config file + */ + onUnRecoverableConfigFileDiagnostic(diagnostic: Diagnostic): void; + } + /** * Host to create watch with config file */ - export interface WatchCompilerHostOfConfigFile extends WatchCompilerHost { + export interface WatchCompilerHostOfConfigFile extends WatchCompilerHost, ConfigFileDiagnosticsReporter { /** Name of the config file to compile */ configFileName: string; /** Options to extend */ optionsToExtend?: CompilerOptions; - // Reports errors in the config file - onConfigFileDiagnostic(diagnostic: Diagnostic): void; + /** + * Used to generate source file names from the config file and its include, exclude, files rules + * and also to cache the directory stucture + */ + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; } /** @@ -241,21 +299,39 @@ namespace ts { * Creates the watch compiler host that can be extended with config file or root file names and options host */ /*@internal*/ - export function createWatchCompilerHost(system = sys, reportDiagnostic?: DiagnosticReporter): WatchCompilerHost { + export function createWatchCompilerHost(system = sys, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHost { return { useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, getNewLine: () => system.newLine, + getCurrentDirectory: getBoundFunction(system.getCurrentDirectory, system), + fileExists: getBoundFunction(system.fileExists, system), + readFile: getBoundFunction(system.readFile, system), + directoryExists: getBoundFunction(system.directoryExists, system), + getDirectories: getBoundFunction(system.getDirectories, system), + readDirectory: getBoundFunction(system.readDirectory, system), + realpath: getBoundFunction(system.realpath, system), system, afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) }; } + /** + * Report error and exit + */ + /*@internal*/ + export function reportUnrecoverableDiagnostic(system: System, reportDiagnostic: DiagnosticReporter, diagnostic: Diagnostic) { + reportDiagnostic(diagnostic); + system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + /** * Create the watched program for config file */ export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { - const host = createWatchCompilerHost(system) as WatchCompilerHostOfConfigFile; - host.onConfigFileDiagnostic = reportDiagnostic || createDiagnosticReporter(system); + reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); + const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfConfigFile; + host.onConfigFileDiagnostic = reportDiagnostic; + host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(system, reportDiagnostic, diagnostic); host.configFileName = configFileName; host.optionsToExtend = optionsToExtend; return createWatch(host); @@ -279,7 +355,7 @@ namespace ts { * Creates the watch from the host for config file */ export function createWatch(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; - export function createWatch(host: WatchCompilerHostOfFilesAndCompilerOptions | WatchCompilerHostOfConfigFile): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { + export function createWatch(host: WatchCompilerHostOfFilesAndCompilerOptions & WatchCompilerHostOfConfigFile): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { interface HostFileInfo { version: number; sourceFile: SourceFile; @@ -297,13 +373,30 @@ namespace ts { let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed - const { system, configFileName, onConfigFileDiagnostic, optionsToExtend: optionsToExtendForConfigFile = {} } = host as WatchCompilerHostOfConfigFile; - const beforeProgramCreate: WatchCompilerHost["beforeProgramCreate"] = host.beforeProgramCreate ? host.beforeProgramCreate.bind(host) : noop; - const afterProgramCreate: WatchCompilerHost["afterProgramCreate"] = host.afterProgramCreate ? host.afterProgramCreate.bind(host) : noop; - let { rootFiles: rootFileNames, options: compilerOptions, configFileSpecs, configFileWildCardDirectories } = host as WatchCompilerHostOfConfigFile; + const system = host.system; + const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames(); + const currentDirectory = host.getCurrentDirectory(); + const getCurrentDirectory = () => currentDirectory; + const onConfigFileDiagnostic = getBoundFunction(host.onConfigFileDiagnostic, host); + const readFile = getBoundFunction(host.readFile, host); + const { configFileName, optionsToExtend: optionsToExtendForConfigFile = {} } = host; + const beforeProgramCreate: WatchCompilerHost["beforeProgramCreate"] = getBoundFunction(host.beforeProgramCreate, host) || noop; + const afterProgramCreate: WatchCompilerHost["afterProgramCreate"] = getBoundFunction(host.afterProgramCreate, host) || noop; + let { rootFiles: rootFileNames, options: compilerOptions, configFileSpecs, configFileWildCardDirectories } = host; + + const cachedDirectoryStructureHost = configFileName && createCachedDirectoryStructureHost(host, currentDirectory, useCaseSensitiveFileNames); + const directoryStructureHost: DirectoryStructureHost = cachedDirectoryStructureHost || host; + const parseConfigFileHost: ParseConfigFileHost = { + useCaseSensitiveFileNames, + readDirectory: getBoundFunction(directoryStructureHost.readDirectory, directoryStructureHost), + fileExists: getBoundFunction(directoryStructureHost.fileExists, directoryStructureHost), + readFile, + getCurrentDirectory, + onConfigFileDiagnostic, + onUnRecoverableConfigFileDiagnostic: getBoundFunction(host.onUnRecoverableConfigFileDiagnostic, host) + }; // From tsc we want to get already parsed result and hence check for rootFileNames - const directoryStructureHost = configFileName ? createCachedDirectoryStructureHost(system) : system; if (configFileName && !rootFileNames) { parseConfigFile(); } @@ -318,11 +411,7 @@ namespace ts { watchFile(system, configFileName, scheduleProgramReload, writeLog); } - const getCurrentDirectory = memoize(() => directoryStructureHost.getCurrentDirectory()); - const realpath = system.realpath && ((path: string) => system.realpath(path)); - const getCachedDirectoryStructureHost = configFileName && (() => directoryStructureHost as CachedDirectoryStructureHost); - const useCaseSensitiveFileNames = memoize(() => host.useCaseSensitiveFileNames()); - const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames()); + const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); let newLine = updateNewLine(); const compilerHost: CompilerHost & ResolutionCacheHost = { @@ -333,23 +422,22 @@ namespace ts { getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), writeFile: notImplemented, getCurrentDirectory, - useCaseSensitiveFileNames, + useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, getCanonicalFileName, getNewLine: () => newLine, fileExists, - readFile: fileName => system.readFile(fileName), + readFile, trace: s => system.write(s + newLine), - directoryExists: directoryName => directoryStructureHost.directoryExists(directoryName), - getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", - getDirectories: path => directoryStructureHost.getDirectories(path), - realpath, + directoryExists: getBoundFunction(directoryStructureHost.directoryExists, directoryStructureHost), + getDirectories: getBoundFunction(directoryStructureHost.getDirectories, directoryStructureHost), + realpath: getBoundFunction(host.realpath, host), onReleaseOldSourceFile, // Members for ResolutionCacheHost toPath, getCompilationSettings: () => compilerOptions, watchDirectoryOfFailedLookupLocation: watchDirectory, watchTypeRootsDirectory: watchDirectory, - getCachedDirectoryStructureHost, + getCachedDirectoryStructureHost: () => cachedDirectoryStructureHost, onInvalidatedResolution: scheduleProgramUpdate, onChangedAutomaticTypeDirectiveNames: () => { hasChangedAutomaticTypeDirectiveNames = true; @@ -359,8 +447,8 @@ namespace ts { }; // Cache for the module resolution const resolutionCache = createResolutionCache(compilerHost, configFileName ? - getDirectoryPath(getNormalizedAbsolutePath(configFileName, getCurrentDirectory())) : - getCurrentDirectory(), + getDirectoryPath(getNormalizedAbsolutePath(configFileName, currentDirectory)) : + currentDirectory, /*logChangesWhenResolvingModule*/ false ); // Resolve module using host module resolution strategy if provided otherwise use resolution cache to resolve module names @@ -442,7 +530,7 @@ namespace ts { } function toPath(fileName: string) { - return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName); + return ts.toPath(fileName, currentDirectory, getCanonicalFileName); } function fileExists(fileName: string) { @@ -505,7 +593,7 @@ namespace ts { let text: string; try { performance.mark("beforeIORead"); - text = system.readFile(fileName, compilerOptions.charset); + text = host.readFile(fileName, compilerOptions.charset); performance.mark("afterIORead"); performance.measure("I/O Read", "beforeIORead", "afterIORead"); } @@ -601,7 +689,7 @@ namespace ts { } function reloadFileNamesFromConfigFile() { - const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, directoryStructureHost); + const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, parseConfigFileHost); if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) { onConfigFileDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); } @@ -615,8 +703,9 @@ namespace ts { writeLog(`Reloading config file: ${configFileName}`); reloadLevel = ConfigFileProgramReloadLevel.None; - const cachedHost = directoryStructureHost as CachedDirectoryStructureHost; - cachedHost.clearCache(); + if (cachedDirectoryStructureHost) { + cachedDirectoryStructureHost.clearCache(); + } parseConfigFile(); hasChangedCompilerOptions = true; synchronizeProgram(); @@ -626,7 +715,7 @@ namespace ts { } function parseConfigFile() { - const configParseResult = ts.parseConfigFile(configFileName, optionsToExtendForConfigFile, directoryStructureHost as CachedDirectoryStructureHost, onConfigFileDiagnostic); + const configParseResult = ts.parseConfigFile(configFileName, optionsToExtendForConfigFile, parseConfigFileHost); rootFileNames = configParseResult.fileNames; compilerOptions = configParseResult.options; configFileSpecs = configParseResult.configFileSpecs; @@ -662,8 +751,8 @@ namespace ts { } function updateCachedSystemWithFile(fileName: string, path: Path, eventKind: FileWatcherEventKind) { - if (configFileName) { - (directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFile(fileName, path, eventKind); + if (cachedDirectoryStructureHost) { + cachedDirectoryStructureHost.addOrDeleteFile(fileName, path, eventKind); } } @@ -712,7 +801,7 @@ namespace ts { const fileOrDirectoryPath = toPath(fileOrDirectory); // Since the file existance changed, update the sourceFiles cache - const result = (directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); + const result = cachedDirectoryStructureHost && cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); // Instead of deleting the file, mark it as changed instead // Many times node calls add/remove/file when watching directories recursively diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index 0cf38f372d9..a1d22c20ac1 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -2,6 +2,247 @@ /* @internal */ namespace ts { + /** + * Partial interface of the System thats needed to support the caching of directory structure + */ + export interface DirectoryStructureHost { + fileExists(path: string): boolean; + readFile(path: string, encoding?: string): string | undefined; + + directoryExists?(path: string): boolean; + getDirectories?(path: string): string[]; + readDirectory?(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + + createDirectory?(path: string): void; + writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void; + } + + interface FileAndDirectoryExistence { + fileExists: boolean; + directoryExists: boolean; + } + + export interface CachedDirectoryStructureHost extends DirectoryStructureHost { + useCaseSensitiveFileNames: boolean; + + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + + /** Returns the queried result for the file exists and directory exists if at all it was done */ + addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path): FileAndDirectoryExistence | undefined; + addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void; + clearCache(): void; + } + + interface MutableFileSystemEntries { + readonly files: string[]; + readonly directories: string[]; + } + + export function createCachedDirectoryStructureHost(host: DirectoryStructureHost, currentDirectory: string, useCaseSensitiveFileNames: boolean): CachedDirectoryStructureHost | undefined { + if (!host.getDirectories || !host.readDirectory) { + return undefined; + } + + const cachedReadDirectoryResult = createMap(); + const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); + return { + useCaseSensitiveFileNames, + fileExists, + readFile: getBoundFunction(host.readFile, host), + directoryExists: host.directoryExists && directoryExists, + getDirectories, + readDirectory, + createDirectory, + writeFile, + addOrDeleteFileOrDirectory, + addOrDeleteFile, + clearCache + }; + + function toPath(fileName: string) { + return ts.toPath(fileName, currentDirectory, getCanonicalFileName); + } + + function getCachedFileSystemEntries(rootDirPath: Path): MutableFileSystemEntries | undefined { + return cachedReadDirectoryResult.get(rootDirPath); + } + + function getCachedFileSystemEntriesForBaseDir(path: Path): MutableFileSystemEntries | undefined { + return getCachedFileSystemEntries(getDirectoryPath(path)); + } + + function getBaseNameOfFileName(fileName: string) { + return getBaseFileName(normalizePath(fileName)); + } + + function createCachedFileSystemEntries(rootDir: string, rootDirPath: Path) { + const resultFromHost: MutableFileSystemEntries = { + files: map(host.readDirectory(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]), getBaseNameOfFileName) || [], + directories: host.getDirectories(rootDir) || [] + }; + + cachedReadDirectoryResult.set(rootDirPath, resultFromHost); + return resultFromHost; + } + + /** + * If the readDirectory result was already cached, it returns that + * Otherwise gets result from host and caches it. + * The host request is done under try catch block to avoid caching incorrect result + */ + function tryReadDirectory(rootDir: string, rootDirPath: Path): MutableFileSystemEntries | undefined { + const cachedResult = getCachedFileSystemEntries(rootDirPath); + if (cachedResult) { + return cachedResult; + } + + try { + return createCachedFileSystemEntries(rootDir, rootDirPath); + } + catch (_e) { + // If there is exception to read directories, dont cache the result and direct the calls to host + Debug.assert(!cachedReadDirectoryResult.has(rootDirPath)); + return undefined; + } + } + + function fileNameEqual(name1: string, name2: string) { + return getCanonicalFileName(name1) === getCanonicalFileName(name2); + } + + function hasEntry(entries: ReadonlyArray, name: string) { + return some(entries, file => fileNameEqual(file, name)); + } + + function updateFileSystemEntry(entries: string[], baseName: string, isValid: boolean) { + if (hasEntry(entries, baseName)) { + if (!isValid) { + return filterMutate(entries, entry => !fileNameEqual(entry, baseName)); + } + } + else if (isValid) { + return entries.push(baseName); + } + } + + function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { + const path = toPath(fileName); + const result = getCachedFileSystemEntriesForBaseDir(path); + if (result) { + updateFilesOfFileSystemEntry(result, getBaseNameOfFileName(fileName), /*fileExists*/ true); + } + return host.writeFile(fileName, data, writeByteOrderMark); + } + + function fileExists(fileName: string): boolean { + const path = toPath(fileName); + const result = getCachedFileSystemEntriesForBaseDir(path); + return result && hasEntry(result.files, getBaseNameOfFileName(fileName)) || + host.fileExists(fileName); + } + + function directoryExists(dirPath: string): boolean { + const path = toPath(dirPath); + return cachedReadDirectoryResult.has(path) || host.directoryExists(dirPath); + } + + function createDirectory(dirPath: string) { + const path = toPath(dirPath); + const result = getCachedFileSystemEntriesForBaseDir(path); + const baseFileName = getBaseNameOfFileName(dirPath); + if (result) { + updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true); + } + host.createDirectory(dirPath); + } + + function getDirectories(rootDir: string): string[] { + const rootDirPath = toPath(rootDir); + const result = tryReadDirectory(rootDir, rootDirPath); + if (result) { + return result.directories.slice(); + } + return host.getDirectories(rootDir); + } + + function readDirectory(rootDir: string, extensions?: ReadonlyArray, excludes?: ReadonlyArray, includes?: ReadonlyArray, depth?: number): string[] { + const rootDirPath = toPath(rootDir); + const result = tryReadDirectory(rootDir, rootDirPath); + if (result) { + return matchFiles(rootDir, extensions, excludes, includes, useCaseSensitiveFileNames, currentDirectory, depth, getFileSystemEntries); + } + return host.readDirectory(rootDir, extensions, excludes, includes, depth); + + function getFileSystemEntries(dir: string) { + const path = toPath(dir); + if (path === rootDirPath) { + return result; + } + return getCachedFileSystemEntries(path) || createCachedFileSystemEntries(dir, path); + } + } + + function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) { + const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath); + if (existingResult) { + // Just clear the cache for now + // For now just clear the cache, since this could mean that multiple level entries might need to be re-evaluated + clearCache(); + return undefined; + } + + const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrDirectoryPath); + if (!parentResult) { + return undefined; + } + + // This was earlier a file (hence not in cached directory contents) + // or we never cached the directory containing it + + if (!host.directoryExists) { + // Since host doesnt support directory exists, clear the cache as otherwise it might not be same + clearCache(); + return undefined; + } + + const baseName = getBaseNameOfFileName(fileOrDirectory); + const fsQueryResult: FileAndDirectoryExistence = { + fileExists: host.fileExists(fileOrDirectoryPath), + directoryExists: host.directoryExists(fileOrDirectoryPath) + }; + if (fsQueryResult.directoryExists || hasEntry(parentResult.directories, baseName)) { + // Folder added or removed, clear the cache instead of updating the folder and its structure + clearCache(); + } + else { + // No need to update the directory structure, just files + updateFilesOfFileSystemEntry(parentResult, baseName, fsQueryResult.fileExists); + } + return fsQueryResult; + + } + + function addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind) { + if (eventKind === FileWatcherEventKind.Changed) { + return; + } + + const parentResult = getCachedFileSystemEntriesForBaseDir(filePath); + if (parentResult) { + updateFilesOfFileSystemEntry(parentResult, getBaseNameOfFileName(fileName), eventKind === FileWatcherEventKind.Created); + } + } + + function updateFilesOfFileSystemEntry(parentResult: MutableFileSystemEntries, baseName: string, fileExists: boolean) { + updateFileSystemEntry(parentResult.files, baseName, fileExists); + } + + function clearCache() { + cachedReadDirectoryResult.clear(); + } + } + export enum ConfigFileProgramReloadLevel { None, /** Update the file name list from the disk */ diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index 92e40cc8f39..5ff1f08332c 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -903,7 +903,7 @@ namespace ts { function verifyProgramWithConfigFile(system: System, configFileName: string) { const program = createWatchOfConfigFile(configFileName, {}, system).getExistingProgram(); - const { fileNames, options } = parseConfigFile(configFileName, {}, system, notImplemented); + const { fileNames, options } = parseConfigFileWithSystem(configFileName, {}, system, notImplemented); verifyProgramIsUptoDate(program, fileNames, options); } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index a5b09c6a63f..bafe90a59cb 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1497,7 +1497,7 @@ namespace ts.server { } private createConfiguredProject(configFileName: NormalizedPath) { - const cachedDirectoryStructureHost = createCachedDirectoryStructureHost(this.host); + const cachedDirectoryStructureHost = createCachedDirectoryStructureHost(this.host, this.host.getCurrentDirectory(), this.host.useCaseSensitiveFileNames); const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedDirectoryStructureHost); this.logger.info(`Opened configuration file ${configFileName}`); const languageServiceEnabled = !this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader); diff --git a/src/server/project.ts b/src/server/project.ts index 40ddaeb2a19..9ae1b1d0b6e 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -334,7 +334,7 @@ namespace ts.server { } useCaseSensitiveFileNames() { - return this.directoryStructureHost.useCaseSensitiveFileNames; + return this.projectService.host.useCaseSensitiveFileNames; } readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] { @@ -342,7 +342,7 @@ namespace ts.server { } readFile(fileName: string): string | undefined { - return this.directoryStructureHost.readFile(fileName); + return this.projectService.host.readFile(fileName); } fileExists(file: string): boolean { @@ -368,6 +368,11 @@ namespace ts.server { return this.directoryStructureHost.getDirectories(path); } + /*@internal*/ + getCachedDirectoryStructureHost(): CachedDirectoryStructureHost { + return undefined; + } + /*@internal*/ toPath(fileName: string) { return toPath(fileName, this.currentDirectory, this.projectService.toCanonicalFileName); @@ -877,7 +882,7 @@ namespace ts.server { missingFilePath, (fileName, eventKind) => { if (this.projectKind === ProjectKind.Configured) { - (this.directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFile(fileName, missingFilePath, eventKind); + this.getCachedDirectoryStructureHost().addOrDeleteFile(fileName, missingFilePath, eventKind); } if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) { diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index f800a1117d0..329d403195d 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -313,7 +313,7 @@ namespace ts.server { detachAllProjects() { for (const p of this.containingProjects) { if (p.projectKind === ProjectKind.Configured) { - (p.directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted); + p.getCachedDirectoryStructureHost().addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted); } const isInfoRoot = p.isRoot(this); // detach is unnecessary since we'll clean the list of containing projects anyways diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index f0f9318e687..64f595664b3 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2477,7 +2477,6 @@ declare namespace ts { * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; - getEnvironmentVariable?(name: string): string; } interface SourceMapRange extends TextRange { source?: SourceMapSource; @@ -2725,26 +2724,14 @@ declare namespace ts { callback: FileWatcherCallback; mtime?: Date; } - /** - * Partial interface of the System thats needed to support the caching of directory structure - */ - interface DirectoryStructureHost { - useCaseSensitiveFileNames: boolean; - readFile(path: string, encoding?: string): string | undefined; - writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; - fileExists(path: string): boolean; - directoryExists(path: string): boolean; - createDirectory(path: string): void; - getCurrentDirectory(): string; - getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; - exit(exitCode?: number): void; - } - interface System extends DirectoryStructureHost { - newLine: string; + interface System { args: string[]; + newLine: string; + useCaseSensitiveFileNames: boolean; write(s: string): void; + readFile(path: string, encoding?: string): string | undefined; getFileSize?(path: string): number; + writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that * use native OS file watching @@ -2752,7 +2739,13 @@ declare namespace ts { watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; resolvePath(path: string): string; + fileExists(path: string): boolean; + directoryExists(path: string): boolean; + createDirectory(path: string): void; getExecutingFilePath(): string; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; getModifiedTime?(path: string): Date; /** * This should be cryptographically secure. @@ -2760,6 +2753,7 @@ declare namespace ts { */ createHash?(data: string): string; getMemoryUsage?(): number; + exit(exitCode?: number): void; realpath?(path: string): string; setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; clearTimeout?(timeoutId: any): void; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 5026237f30e..c74fc46d60e 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2477,7 +2477,6 @@ declare namespace ts { * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files */ resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; - getEnvironmentVariable?(name: string): string; } interface SourceMapRange extends TextRange { source?: SourceMapSource; @@ -2725,26 +2724,14 @@ declare namespace ts { callback: FileWatcherCallback; mtime?: Date; } - /** - * Partial interface of the System thats needed to support the caching of directory structure - */ - interface DirectoryStructureHost { - useCaseSensitiveFileNames: boolean; - readFile(path: string, encoding?: string): string | undefined; - writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; - fileExists(path: string): boolean; - directoryExists(path: string): boolean; - createDirectory(path: string): void; - getCurrentDirectory(): string; - getDirectories(path: string): string[]; - readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; - exit(exitCode?: number): void; - } - interface System extends DirectoryStructureHost { - newLine: string; + interface System { args: string[]; + newLine: string; + useCaseSensitiveFileNames: boolean; write(s: string): void; + readFile(path: string, encoding?: string): string | undefined; getFileSize?(path: string): number; + writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that * use native OS file watching @@ -2752,7 +2739,13 @@ declare namespace ts { watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; resolvePath(path: string): string; + fileExists(path: string): boolean; + directoryExists(path: string): boolean; + createDirectory(path: string): void; getExecutingFilePath(): string; + getCurrentDirectory(): string; + getDirectories(path: string): string[]; + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; getModifiedTime?(path: string): Date; /** * This should be cryptographically secure. @@ -2760,6 +2753,7 @@ declare namespace ts { */ createHash?(data: string): string; getMemoryUsage?(): number; + exit(exitCode?: number): void; realpath?(path: string): string; setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; clearTimeout?(timeoutId: any): void; @@ -3829,7 +3823,26 @@ declare namespace ts { afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; useCaseSensitiveFileNames(): boolean; getNewLine(): string; - /** If provided this function would be used to resolve the module names, otherwise typescript's default module resolution */ + getCurrentDirectory(): string; + /** + * Use to check file presence for source files and + * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well + */ + fileExists(path: string): boolean; + /** + * Use to read file text for source files and + * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well + */ + readFile(path: string, encoding?: string): string | undefined; + /** If provided, used for module resolution as well as to handle directory structure */ + directoryExists?(path: string): boolean; + /** If provided, used in resolutions as well as handling directory structure */ + getDirectories?(path: string): string[]; + /** If provided, used to cache and handle directory structure modifications */ + readDirectory?(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + /** Symbol links resolution */ + realpath?(path: string): string; + /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; } /** @@ -3841,15 +3854,32 @@ declare namespace ts { /** Compiler options */ options: CompilerOptions; } + /** + * Reports config file diagnostics + */ + interface ConfigFileDiagnosticsReporter { + /** + * Reports the diagnostics in reading/writing or parsing of the config file + */ + onConfigFileDiagnostic(diagnostic: Diagnostic): void; + /** + * Reports unrecoverable error when parsing config file + */ + onUnRecoverableConfigFileDiagnostic(diagnostic: Diagnostic): void; + } /** * Host to create watch with config file */ - interface WatchCompilerHostOfConfigFile extends WatchCompilerHost { + interface WatchCompilerHostOfConfigFile extends WatchCompilerHost, ConfigFileDiagnosticsReporter { /** Name of the config file to compile */ configFileName: string; /** Options to extend */ optionsToExtend?: CompilerOptions; - onConfigFileDiagnostic(diagnostic: Diagnostic): void; + /** + * Used to generate source file names from the config file and its include, exclude, files rules + * and also to cache the directory stucture + */ + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; } interface Watch { /** Synchronize with host and get updated program */ From e694b9e3bae0deb094f52652bc1a94be85e7efef Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Dec 2017 17:50:14 -0800 Subject: [PATCH 024/172] Update the WatchCompilerHost creation --- src/compiler/tsc.ts | 17 ++++++----------- src/compiler/watch.ts | 36 ++++++++++++++++++++++++++---------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index daccfca33b8..43211313237 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -21,7 +21,7 @@ namespace ts { return diagnostic.messageText; } - let reportDiagnostic = createDiagnosticReporter(); + let reportDiagnostic = createDiagnosticReporter(sys); function udpateReportDiagnostic(options: CompilerOptions) { if (options.pretty) { reportDiagnostic = createDiagnosticReporter(sys, /*pretty*/ true); @@ -150,33 +150,28 @@ namespace ts { return sys.exit(exitStatus); } - function createWatchCompilerHost(): WatchCompilerHost { - const watchCompilerHost = ts.createWatchCompilerHost(sys, reportDiagnostic); + function updateWatchCompilationHost(watchCompilerHost: WatchCompilerHost) { const compilerWithBuilderState = watchCompilerHost.afterProgramCreate; watchCompilerHost.beforeProgramCreate = enableStatistics; watchCompilerHost.afterProgramCreate = (host, program) => { compilerWithBuilderState(host, program); reportStatistics(program); }; - return watchCompilerHost; } function createWatchOfConfigFile(configParseResult: ParsedCommandLine, optionsToExtend: CompilerOptions) { - const watchCompilerHost = createWatchCompilerHost() as WatchCompilerHostOfConfigFile; - watchCompilerHost.onConfigFileDiagnostic = reportDiagnostic; + const watchCompilerHost = ts.createWatchCompilerHostOfConfigFile(configParseResult.options.configFilePath, optionsToExtend, sys, reportDiagnostic); + updateWatchCompilationHost(watchCompilerHost); watchCompilerHost.rootFiles = configParseResult.fileNames; watchCompilerHost.options = configParseResult.options; - watchCompilerHost.configFileName = configParseResult.options.configFilePath; - watchCompilerHost.optionsToExtend = optionsToExtend; watchCompilerHost.configFileSpecs = configParseResult.configFileSpecs; watchCompilerHost.configFileWildCardDirectories = configParseResult.wildcardDirectories; createWatch(watchCompilerHost); } function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions) { - const watchCompilerHost = createWatchCompilerHost() as WatchCompilerHostOfFilesAndCompilerOptions; - watchCompilerHost.rootFiles = rootFiles; - watchCompilerHost.options = options; + const watchCompilerHost = ts.createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, sys, reportDiagnostic); + updateWatchCompilationHost(watchCompilerHost); createWatch(watchCompilerHost); } diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 0bd0fc499b7..2653be5e20d 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -15,7 +15,7 @@ namespace ts { * Create a function that reports error by writing to the system and handles the formating of the diagnostic */ /*@internal*/ - export function createDiagnosticReporter(system = sys, pretty?: boolean): DiagnosticReporter { + export function createDiagnosticReporter(system: System, pretty?: boolean): DiagnosticReporter { const host: FormatDiagnosticsHost = system === sys ? sysFormatDiagnosticsHost : { getCurrentDirectory: () => system.getCurrentDirectory(), getNewLine: () => system.newLine, @@ -266,6 +266,7 @@ namespace ts { */ /*@internal*/ export interface WatchCompilerHostOfConfigFile extends WatchCompilerHost { + cachedDirectoryStructureHost?: CachedDirectoryStructureHost; rootFiles?: string[]; options?: CompilerOptions; optionsToExtend?: CompilerOptions; @@ -298,8 +299,7 @@ namespace ts { /** * Creates the watch compiler host that can be extended with config file or root file names and options host */ - /*@internal*/ - export function createWatchCompilerHost(system = sys, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHost { + function createWatchCompilerHost(system = sys, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHost { return { useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, getNewLine: () => system.newLine, @@ -325,26 +325,42 @@ namespace ts { } /** - * Create the watched program for config file + * Creates the watch compiler host from system for config file in watch mode */ - export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { + /*@internal*/ + export function createWatchCompilerHostOfConfigFile(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHostOfConfigFile { reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfConfigFile; host.onConfigFileDiagnostic = reportDiagnostic; host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(system, reportDiagnostic, diagnostic); host.configFileName = configFileName; host.optionsToExtend = optionsToExtend; - return createWatch(host); + return host; + } + + /** + * Creates the watch compiler host from system for compiling root files and options in watch mode + */ + /*@internal*/ + export function createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHostOfFilesAndCompilerOptions { + const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfFilesAndCompilerOptions; + host.rootFiles = rootFiles; + host.options = options; + return host; + } + + /** + * Create the watched program for config file + */ + export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { + return createWatch(createWatchCompilerHostOfConfigFile(configFileName, optionsToExtend, system, reportDiagnostic)); } /** * Create the watched program for root files and compiler options */ export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfFilesAndCompilerOptions { - const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfFilesAndCompilerOptions; - host.rootFiles = rootFiles; - host.options = options; - return createWatch(host); + return createWatch(createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, system, reportDiagnostic)); } /** From 8cc293635229b053632b52493988ec4ae781e812 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Dec 2017 18:09:10 -0800 Subject: [PATCH 025/172] Move watchFile and watchDirectory to WatchCompilerHost --- src/compiler/watch.ts | 17 +++++++---- src/compiler/watchUtilities.ts | 30 ++++++++++++------- src/harness/unittests/session.ts | 3 ++ src/server/types.ts | 4 ++- .../reference/api/tsserverlibrary.d.ts | 2 ++ tests/baselines/reference/api/typescript.d.ts | 4 +++ 6 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 2653be5e20d..210c1efe409 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -216,6 +216,11 @@ namespace ts { /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + + /** Used to watch changes in source files, missing files needed to update the program or config file */ + watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; + /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ + watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; } /** @@ -310,6 +315,8 @@ namespace ts { getDirectories: getBoundFunction(system.getDirectories, system), readDirectory: getBoundFunction(system.readDirectory, system), realpath: getBoundFunction(system.realpath, system), + watchFile: getBoundFunction(system.watchFile, system), + watchDirectory: getBoundFunction(system.watchDirectory, system), system, afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) }; @@ -424,7 +431,7 @@ namespace ts { const watchDirectoryWorker = compilerOptions.extendedDiagnostics ? ts.addDirectoryWatcherWithLogging : ts.addDirectoryWatcher; if (configFileName) { - watchFile(system, configFileName, scheduleProgramReload, writeLog); + watchFile(host, configFileName, scheduleProgramReload, writeLog); } const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); @@ -581,7 +588,7 @@ namespace ts { hostSourceFile.sourceFile = sourceFile; sourceFile.version = hostSourceFile.version.toString(); if (!hostSourceFile.fileWatcher) { - hostSourceFile.fileWatcher = watchFilePath(system, fileName, onSourceFileChange, path, writeLog); + hostSourceFile.fileWatcher = watchFilePath(host, fileName, onSourceFileChange, path, writeLog); } } else { @@ -594,7 +601,7 @@ namespace ts { let fileWatcher: FileWatcher; if (sourceFile) { sourceFile.version = "1"; - fileWatcher = watchFilePath(system, fileName, onSourceFileChange, path, writeLog); + fileWatcher = watchFilePath(host, fileName, onSourceFileChange, path, writeLog); sourceFilesCache.set(path, { sourceFile, version: 1, fileWatcher }); } else { @@ -773,11 +780,11 @@ namespace ts { } function watchDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) { - return watchDirectoryWorker(system, directory, cb, flags, writeLog); + return watchDirectoryWorker(host, directory, cb, flags, writeLog); } function watchMissingFilePath(missingFilePath: Path) { - return watchFilePath(system, missingFilePath, onMissingFileChange, missingFilePath, writeLog); + return watchFilePath(host, missingFilePath, onMissingFileChange, missingFilePath, writeLog); } function onMissingFileChange(fileName: string, eventKind: FileWatcherEventKind, missingFilePath: Path) { diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index a1d22c20ac1..bf5a42b7b08 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -323,53 +323,61 @@ namespace ts { } } - export function addFileWatcher(host: System, file: string, cb: FileWatcherCallback): FileWatcher { + export interface WatchFileHost { + watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; + } + + export function addFileWatcher(host: WatchFileHost, file: string, cb: FileWatcherCallback): FileWatcher { return host.watchFile(file, cb); } - export function addFileWatcherWithLogging(host: System, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher { + export function addFileWatcherWithLogging(host: WatchFileHost, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher { const watcherCaption = `FileWatcher:: `; return createWatcherWithLogging(addFileWatcher, watcherCaption, log, /*logOnlyTrigger*/ false, host, file, cb); } - export function addFileWatcherWithOnlyTriggerLogging(host: System, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher { + export function addFileWatcherWithOnlyTriggerLogging(host: WatchFileHost, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher { const watcherCaption = `FileWatcher:: `; return createWatcherWithLogging(addFileWatcher, watcherCaption, log, /*logOnlyTrigger*/ true, host, file, cb); } export type FilePathWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void; - export function addFilePathWatcher(host: System, file: string, cb: FilePathWatcherCallback, path: Path): FileWatcher { + export function addFilePathWatcher(host: WatchFileHost, file: string, cb: FilePathWatcherCallback, path: Path): FileWatcher { return host.watchFile(file, (fileName, eventKind) => cb(fileName, eventKind, path)); } - export function addFilePathWatcherWithLogging(host: System, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher { + export function addFilePathWatcherWithLogging(host: WatchFileHost, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher { const watcherCaption = `FileWatcher:: `; return createWatcherWithLogging(addFileWatcher, watcherCaption, log, /*logOnlyTrigger*/ false, host, file, cb, path); } - export function addFilePathWatcherWithOnlyTriggerLogging(host: System, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher { + export function addFilePathWatcherWithOnlyTriggerLogging(host: WatchFileHost, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher { const watcherCaption = `FileWatcher:: `; return createWatcherWithLogging(addFileWatcher, watcherCaption, log, /*logOnlyTrigger*/ true, host, file, cb, path); } - export function addDirectoryWatcher(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher { + export interface WatchDirectoryHost { + watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; + } + + export function addDirectoryWatcher(host: WatchDirectoryHost, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher { const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0; return host.watchDirectory(directory, cb, recursive); } - export function addDirectoryWatcherWithLogging(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher { + export function addDirectoryWatcherWithLogging(host: WatchDirectoryHost, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher { const watcherCaption = `DirectoryWatcher ${(flags & WatchDirectoryFlags.Recursive) !== 0 ? "recursive" : ""}:: `; return createWatcherWithLogging(addDirectoryWatcher, watcherCaption, log, /*logOnlyTrigger*/ false, host, directory, cb, flags); } - export function addDirectoryWatcherWithOnlyTriggerLogging(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher { + export function addDirectoryWatcherWithOnlyTriggerLogging(host: WatchDirectoryHost, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher { const watcherCaption = `DirectoryWatcher ${(flags & WatchDirectoryFlags.Recursive) !== 0 ? "recursive" : ""}:: `; return createWatcherWithLogging(addDirectoryWatcher, watcherCaption, log, /*logOnlyTrigger*/ true, host, directory, cb, flags); } type WatchCallback = (fileName: string, cbOptional1?: T, optional?: U) => void; - type AddWatch = (host: System, file: string, cb: WatchCallback, optional?: U) => FileWatcher; - function createWatcherWithLogging(addWatch: AddWatch, watcherCaption: string, log: (s: string) => void, logOnlyTrigger: boolean, host: System, file: string, cb: WatchCallback, optional?: U): FileWatcher { + type AddWatch = (host: H, file: string, cb: WatchCallback, optional?: U) => FileWatcher; + function createWatcherWithLogging(addWatch: AddWatch, watcherCaption: string, log: (s: string) => void, logOnlyTrigger: boolean, host: H, file: string, cb: WatchCallback, optional?: U): FileWatcher { const info = `PathInfo: ${file}`; if (!logOnlyTrigger) { log(`${watcherCaption}Added: ${info}`); diff --git a/src/harness/unittests/session.ts b/src/harness/unittests/session.ts index 9912294d1d2..7d71d83c754 100644 --- a/src/harness/unittests/session.ts +++ b/src/harness/unittests/session.ts @@ -2,6 +2,7 @@ namespace ts.server { let lastWrittenToHost: string; + const noopFileWatcher: FileWatcher = { close: noop }; const mockHost: ServerHost = { args: [], newLine: "\n", @@ -24,6 +25,8 @@ namespace ts.server { setImmediate: () => 0, clearImmediate: noop, createHash: Harness.mockHash, + watchFile: () => noopFileWatcher, + watchDirectory: () => noopFileWatcher }; class TestSession extends Session { diff --git a/src/server/types.ts b/src/server/types.ts index 32132ed278b..93ffeeccff1 100644 --- a/src/server/types.ts +++ b/src/server/types.ts @@ -11,6 +11,8 @@ declare namespace ts.server { type RequireResult = { module: {}, error: undefined } | { module: undefined, error: { stack?: string, message?: string } }; export interface ServerHost extends System { + watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; + watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; clearTimeout(timeoutId: any): void; setImmediate(callback: (...args: any[]) => void, ...args: any[]): any; @@ -129,4 +131,4 @@ declare namespace ts.server { createDirectory(path: string): void; watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; } -} \ No newline at end of file +} diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 64f595664b3..ca399847583 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4747,6 +4747,8 @@ declare namespace ts.server { }; }; interface ServerHost extends System { + watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; + watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; clearTimeout(timeoutId: any): void; setImmediate(callback: (...args: any[]) => void, ...args: any[]): any; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index c74fc46d60e..6f347efc46b 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3844,6 +3844,10 @@ declare namespace ts { realpath?(path: string): string; /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + /** Used to watch changes in source files, missing files needed to update the program or config file */ + watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; + /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ + watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; } /** * Host to create watch with root files and options From abafddded23c4d02332ca9072b44c63ccde34e47 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Dec 2017 18:13:45 -0800 Subject: [PATCH 026/172] Move internal functions in the watch to separate namespace --- src/compiler/watch.ts | 118 ++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 61 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 210c1efe409..ac9ef086fbe 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -2,9 +2,8 @@ /// /// +/*@internal*/ namespace ts { - export type DiagnosticReporter = (diagnostic: Diagnostic) => void; - const sysFormatDiagnosticsHost: FormatDiagnosticsHost = sys ? { getCurrentDirectory: () => sys.getCurrentDirectory(), getNewLine: () => sys.newLine, @@ -14,7 +13,6 @@ namespace ts { /** * Create a function that reports error by writing to the system and handles the formating of the diagnostic */ - /*@internal*/ export function createDiagnosticReporter(system: System, pretty?: boolean): DiagnosticReporter { const host: FormatDiagnosticsHost = system === sys ? sysFormatDiagnosticsHost : { getCurrentDirectory: () => system.getCurrentDirectory(), @@ -36,13 +34,11 @@ namespace ts { /** * Interface extending ParseConfigHost to support ParseConfigFile that reads config file and reports errors */ - /*@internal*/ export interface ParseConfigFileHost extends ParseConfigHost, ConfigFileDiagnosticsReporter { getCurrentDirectory(): string; } /** Parses config file using System interface */ - /*@internal*/ export function parseConfigFileWithSystem(configFileName: string, optionsToExtend: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter) { const host: ParseConfigFileHost = system; host.onConfigFileDiagnostic = reportDiagnostic; @@ -56,7 +52,6 @@ namespace ts { /** * Reads the config file, reports errors if any and exits if the config file cannot be found */ - /*@internal*/ export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost): ParsedCommandLine | undefined { let configFileText: string; try { @@ -83,6 +78,62 @@ namespace ts { return configParseResult; } + /** + * Creates the watch compiler host that can be extended with config file or root file names and options host + */ + function createWatchCompilerHost(system = sys, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHost { + return { + useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, + getNewLine: () => system.newLine, + getCurrentDirectory: getBoundFunction(system.getCurrentDirectory, system), + fileExists: getBoundFunction(system.fileExists, system), + readFile: getBoundFunction(system.readFile, system), + directoryExists: getBoundFunction(system.directoryExists, system), + getDirectories: getBoundFunction(system.getDirectories, system), + readDirectory: getBoundFunction(system.readDirectory, system), + realpath: getBoundFunction(system.realpath, system), + watchFile: getBoundFunction(system.watchFile, system), + watchDirectory: getBoundFunction(system.watchDirectory, system), + system, + afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) + }; + } + + /** + * Report error and exit + */ + export function reportUnrecoverableDiagnostic(system: System, reportDiagnostic: DiagnosticReporter, diagnostic: Diagnostic) { + reportDiagnostic(diagnostic); + system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); + } + + /** + * Creates the watch compiler host from system for config file in watch mode + */ + export function createWatchCompilerHostOfConfigFile(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHostOfConfigFile { + reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); + const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfConfigFile; + host.onConfigFileDiagnostic = reportDiagnostic; + host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(system, reportDiagnostic, diagnostic); + host.configFileName = configFileName; + host.optionsToExtend = optionsToExtend; + return host; + } + + /** + * Creates the watch compiler host from system for compiling root files and options in watch mode + */ + export function createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHostOfFilesAndCompilerOptions { + const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfFilesAndCompilerOptions; + host.rootFiles = rootFiles; + host.options = options; + return host; + } +} + +namespace ts { + export type DiagnosticReporter = (diagnostic: Diagnostic) => void; + /** * Writes emitted files, source files depending on options */ @@ -301,61 +352,6 @@ namespace ts { updateRootFileNames(fileNames: string[]): void; } - /** - * Creates the watch compiler host that can be extended with config file or root file names and options host - */ - function createWatchCompilerHost(system = sys, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHost { - return { - useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, - getNewLine: () => system.newLine, - getCurrentDirectory: getBoundFunction(system.getCurrentDirectory, system), - fileExists: getBoundFunction(system.fileExists, system), - readFile: getBoundFunction(system.readFile, system), - directoryExists: getBoundFunction(system.directoryExists, system), - getDirectories: getBoundFunction(system.getDirectories, system), - readDirectory: getBoundFunction(system.readDirectory, system), - realpath: getBoundFunction(system.realpath, system), - watchFile: getBoundFunction(system.watchFile, system), - watchDirectory: getBoundFunction(system.watchDirectory, system), - system, - afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) - }; - } - - /** - * Report error and exit - */ - /*@internal*/ - export function reportUnrecoverableDiagnostic(system: System, reportDiagnostic: DiagnosticReporter, diagnostic: Diagnostic) { - reportDiagnostic(diagnostic); - system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); - } - - /** - * Creates the watch compiler host from system for config file in watch mode - */ - /*@internal*/ - export function createWatchCompilerHostOfConfigFile(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHostOfConfigFile { - reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); - const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfConfigFile; - host.onConfigFileDiagnostic = reportDiagnostic; - host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(system, reportDiagnostic, diagnostic); - host.configFileName = configFileName; - host.optionsToExtend = optionsToExtend; - return host; - } - - /** - * Creates the watch compiler host from system for compiling root files and options in watch mode - */ - /*@internal*/ - export function createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHostOfFilesAndCompilerOptions { - const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfFilesAndCompilerOptions; - host.rootFiles = rootFiles; - host.options = options; - return host; - } - /** * Create the watched program for config file */ From 77e67311aad296b934b41acfec6b32542402b6c5 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Dec 2017 18:37:57 -0800 Subject: [PATCH 027/172] Handle setTimeout, clearTimeout, clearScreen and report watch Diagnostics --- src/compiler/watch.ts | 49 +++++++++++-------- tests/baselines/reference/api/typescript.d.ts | 6 +++ 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index ac9ef086fbe..fb0af18ef5b 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -94,15 +94,25 @@ namespace ts { realpath: getBoundFunction(system.realpath, system), watchFile: getBoundFunction(system.watchFile, system), watchDirectory: getBoundFunction(system.watchDirectory, system), + setTimeout: getBoundFunction(system.setTimeout, system), + clearTimeout: getBoundFunction(system.clearTimeout, system), + onWatchStatusChange, system, afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) }; + + function onWatchStatusChange(diagnostic: Diagnostic, newLine: string) { + if (system.clearScreen && diagnostic.code !== Diagnostics.Compilation_complete_Watching_for_file_changes.code) { + system.clearScreen(); + } + system.write(`${new Date().toLocaleTimeString()} - ${flattenDiagnosticMessageText(diagnostic.messageText, newLine)}${newLine + newLine + newLine}`); + } } /** * Report error and exit */ - export function reportUnrecoverableDiagnostic(system: System, reportDiagnostic: DiagnosticReporter, diagnostic: Diagnostic) { + function reportUnrecoverableDiagnostic(system: System, reportDiagnostic: DiagnosticReporter, diagnostic: Diagnostic) { reportDiagnostic(diagnostic); system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); } @@ -238,6 +248,8 @@ namespace ts { beforeProgramCreate?(compilerOptions: CompilerOptions): void; /** If provided, callback to invoke after every new program creation */ afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; + /** If provided, called with Diagnostic message that informs about change in watch status */ + onWatchStatusChange?(diagnostic: Diagnostic, newLine: string): void; // Sub set of compiler host methods to read and generate new program useCaseSensitiveFileNames(): boolean; @@ -272,6 +284,10 @@ namespace ts { watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; + /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together*/ + setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; + /** If provided, will be used to reset existing delayed compilation */ + clearTimeout?(timeoutId: any): void; } /** @@ -399,8 +415,8 @@ namespace ts { const onConfigFileDiagnostic = getBoundFunction(host.onConfigFileDiagnostic, host); const readFile = getBoundFunction(host.readFile, host); const { configFileName, optionsToExtend: optionsToExtendForConfigFile = {} } = host; - const beforeProgramCreate: WatchCompilerHost["beforeProgramCreate"] = getBoundFunction(host.beforeProgramCreate, host) || noop; - const afterProgramCreate: WatchCompilerHost["afterProgramCreate"] = getBoundFunction(host.afterProgramCreate, host) || noop; + const beforeProgramCreate = getBoundFunction(host.beforeProgramCreate, host) || noop; + const afterProgramCreate = getBoundFunction(host.afterProgramCreate, host) || noop; let { rootFiles: rootFileNames, options: compilerOptions, configFileSpecs, configFileWildCardDirectories } = host; const cachedDirectoryStructureHost = configFileName && createCachedDirectoryStructureHost(host, currentDirectory, useCaseSensitiveFileNames); @@ -476,8 +492,7 @@ namespace ts { resolutionCache.resolveModuleNames.bind(resolutionCache); compilerHost.resolveTypeReferenceDirectives = resolutionCache.resolveTypeReferenceDirectives.bind(resolutionCache); - clearHostScreen(); - reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Starting_compilation_in_watch_mode)); + reportWatchDiagnostic(Diagnostics.Starting_compilation_in_watch_mode); synchronizeProgram(); // Update the wild card directory watch @@ -534,7 +549,7 @@ namespace ts { } afterProgramCreate(directoryStructureHost, program); - reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); + reportWatchDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes); return program; } @@ -660,22 +675,24 @@ namespace ts { } } - function reportWatchDiagnostic(diagnostic: Diagnostic) { - system.write(`${new Date().toLocaleTimeString()} - ${flattenDiagnosticMessageText(diagnostic.messageText, newLine)}${newLine + newLine + newLine}`); + function reportWatchDiagnostic(message: DiagnosticMessage) { + if (host.onWatchStatusChange) { + host.onWatchStatusChange(createCompilerDiagnostic(message), newLine); + } } // Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch // operations (such as saving all modified files in an editor) a chance to complete before we kick // off a new compilation. function scheduleProgramUpdate() { - if (!system.setTimeout || !system.clearTimeout) { + if (!host.setTimeout || !host.clearTimeout) { return; } if (timerToUpdateProgram) { - system.clearTimeout(timerToUpdateProgram); + host.clearTimeout(timerToUpdateProgram); } - timerToUpdateProgram = system.setTimeout(updateProgram, 250); + timerToUpdateProgram = host.setTimeout(updateProgram, 250); } function scheduleProgramReload() { @@ -684,17 +701,9 @@ namespace ts { scheduleProgramUpdate(); } - function clearHostScreen() { - if (system.clearScreen) { - system.clearScreen(); - } - } - function updateProgram() { - clearHostScreen(); - timerToUpdateProgram = undefined; - reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation)); + reportWatchDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation); switch (reloadLevel) { case ConfigFileProgramReloadLevel.Partial: diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 6f347efc46b..7031f305008 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3821,6 +3821,8 @@ declare namespace ts { beforeProgramCreate?(compilerOptions: CompilerOptions): void; /** If provided, callback to invoke after every new program creation */ afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; + /** If provided, called with Diagnostic message that informs about change in watch status */ + onWatchStatusChange?(diagnostic: Diagnostic, newLine: string): void; useCaseSensitiveFileNames(): boolean; getNewLine(): string; getCurrentDirectory(): string; @@ -3848,6 +3850,10 @@ declare namespace ts { watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; + /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together*/ + setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; + /** If provided, will be used to reset existing delayed compilation */ + clearTimeout?(timeoutId: any): void; } /** * Host to create watch with root files and options From d22ba5e9650d2434cb5200171ee45436e38c32de Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Dec 2017 18:50:50 -0800 Subject: [PATCH 028/172] Move the system.write to trace on WatchCompilerHost --- src/compiler/watch.ts | 10 +++++++--- tests/baselines/reference/api/typescript.d.ts | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index fb0af18ef5b..f23f94d62c2 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -96,6 +96,7 @@ namespace ts { watchDirectory: getBoundFunction(system.watchDirectory, system), setTimeout: getBoundFunction(system.setTimeout, system), clearTimeout: getBoundFunction(system.clearTimeout, system), + trace: getBoundFunction(system.write, system), onWatchStatusChange, system, afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) @@ -276,6 +277,8 @@ namespace ts { /** Symbol links resolution */ realpath?(path: string): string; + /** If provided would be used to write log about compilation */ + trace?(s: string): void; /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; @@ -436,8 +439,9 @@ namespace ts { parseConfigFile(); } - const loggingEnabled = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; - const writeLog: (s: string) => void = loggingEnabled ? s => { system.write(s); system.write(newLine); } : noop; + const trace = host.trace && ((s: string) => { host.trace(s + newLine) }); + const loggingEnabled = trace && (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics); + const writeLog = loggingEnabled ? trace : noop; const watchFile = compilerOptions.extendedDiagnostics ? ts.addFileWatcherWithLogging : loggingEnabled ? ts.addFileWatcherWithOnlyTriggerLogging : ts.addFileWatcher; const watchFilePath = compilerOptions.extendedDiagnostics ? ts.addFilePathWatcherWithLogging : ts.addFilePathWatcher; const watchDirectoryWorker = compilerOptions.extendedDiagnostics ? ts.addDirectoryWatcherWithLogging : ts.addDirectoryWatcher; @@ -462,7 +466,7 @@ namespace ts { getNewLine: () => newLine, fileExists, readFile, - trace: s => system.write(s + newLine), + trace, directoryExists: getBoundFunction(directoryStructureHost.directoryExists, directoryStructureHost), getDirectories: getBoundFunction(directoryStructureHost.getDirectories, directoryStructureHost), realpath: getBoundFunction(host.realpath, host), diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 7031f305008..c676aa951ac 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3844,6 +3844,8 @@ declare namespace ts { readDirectory?(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; /** Symbol links resolution */ realpath?(path: string): string; + /** If provided would be used to write log about compilation */ + trace?(s: string): void; /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; /** Used to watch changes in source files, missing files needed to update the program or config file */ From c9a407e5533475463f15fb268e648dd10c5e9b0d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Dec 2017 19:00:06 -0800 Subject: [PATCH 029/172] Add getDefaultLibLocation and getDefaultLibFileName and remove system from WatchCompilerHost --- src/compiler/watch.ts | 25 +++++++++---------- tests/baselines/reference/api/typescript.d.ts | 4 +-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index f23f94d62c2..67caf3c79ca 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -86,6 +86,8 @@ namespace ts { useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, getNewLine: () => system.newLine, getCurrentDirectory: getBoundFunction(system.getCurrentDirectory, system), + getDefaultLibLocation, + getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), fileExists: getBoundFunction(system.fileExists, system), readFile: getBoundFunction(system.readFile, system), directoryExists: getBoundFunction(system.directoryExists, system), @@ -98,10 +100,13 @@ namespace ts { clearTimeout: getBoundFunction(system.clearTimeout, system), trace: getBoundFunction(system.write, system), onWatchStatusChange, - system, afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) }; + function getDefaultLibLocation() { + return getDirectoryPath(normalizePath(system.getExecutingFilePath())); + } + function onWatchStatusChange(diagnostic: Diagnostic, newLine: string) { if (system.clearScreen && diagnostic.code !== Diagnostics.Compilation_complete_Watching_for_file_changes.code) { system.clearScreen(); @@ -242,9 +247,6 @@ namespace ts { } export interface WatchCompilerHost { - /** FS system to use */ - system: System; - /** If provided, callback to invoke before each program creation */ beforeProgramCreate?(compilerOptions: CompilerOptions): void; /** If provided, callback to invoke after every new program creation */ @@ -256,6 +258,8 @@ namespace ts { useCaseSensitiveFileNames(): boolean; getNewLine(): string; getCurrentDirectory(): string; + getDefaultLibFileName(options: CompilerOptions): string; + getDefaultLibLocation?(): string; /** * Use to check file presence for source files and @@ -287,7 +291,7 @@ namespace ts { watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; - /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together*/ + /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */ setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; /** If provided, will be used to reset existing delayed compilation */ clearTimeout?(timeoutId: any): void; @@ -411,7 +415,6 @@ namespace ts { let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed - const system = host.system; const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames(); const currentDirectory = host.getCurrentDirectory(); const getCurrentDirectory = () => currentDirectory; @@ -439,7 +442,7 @@ namespace ts { parseConfigFile(); } - const trace = host.trace && ((s: string) => { host.trace(s + newLine) }); + const trace = host.trace && ((s: string) => { host.trace(s + newLine); }); const loggingEnabled = trace && (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics); const writeLog = loggingEnabled ? trace : noop; const watchFile = compilerOptions.extendedDiagnostics ? ts.addFileWatcherWithLogging : loggingEnabled ? ts.addFileWatcherWithOnlyTriggerLogging : ts.addFileWatcher; @@ -457,8 +460,8 @@ namespace ts { // Members for CompilerHost getSourceFile: (fileName, languageVersion, onError?, shouldCreateNewSourceFile?) => getVersionedSourceFileByPath(fileName, toPath(fileName), languageVersion, onError, shouldCreateNewSourceFile), getSourceFileByPath: getVersionedSourceFileByPath, - getDefaultLibLocation, - getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), + getDefaultLibLocation: getBoundFunction(host.getDefaultLibLocation, host), + getDefaultLibFileName: getBoundFunction(host.getDefaultLibFileName, host), writeFile: notImplemented, getCurrentDirectory, useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, @@ -581,10 +584,6 @@ namespace ts { return directoryStructureHost.fileExists(fileName); } - function getDefaultLibLocation(): string { - return getDirectoryPath(normalizePath(system.getExecutingFilePath())); - } - function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { const hostSourceFile = sourceFilesCache.get(path); // No source file on the host diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index c676aa951ac..521fd1e0ad6 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3815,8 +3815,6 @@ declare namespace ts { */ function createProgramCompilerWithBuilderState(system?: System, reportDiagnostic?: DiagnosticReporter): (host: DirectoryStructureHost, program: Program) => void; interface WatchCompilerHost { - /** FS system to use */ - system: System; /** If provided, callback to invoke before each program creation */ beforeProgramCreate?(compilerOptions: CompilerOptions): void; /** If provided, callback to invoke after every new program creation */ @@ -3826,6 +3824,8 @@ declare namespace ts { useCaseSensitiveFileNames(): boolean; getNewLine(): string; getCurrentDirectory(): string; + getDefaultLibFileName(options: CompilerOptions): string; + getDefaultLibLocation?(): string; /** * Use to check file presence for source files and * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well From 14f66efcc595f51adf612167bc68dc745a3265de Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 5 Dec 2017 20:23:14 -0800 Subject: [PATCH 030/172] Update the emitting file, reporting errors part of the watch api --- src/compiler/core.ts | 4 - src/compiler/tsc.ts | 43 +-- src/compiler/watch.ts | 357 ++++++++++++------ src/compiler/watchUtilities.ts | 6 +- src/server/editorServices.ts | 4 +- src/server/project.ts | 6 +- .../reference/api/tsserverlibrary.d.ts | 5 +- tests/baselines/reference/api/typescript.d.ts | 22 +- 8 files changed, 266 insertions(+), 181 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index b6472987398..1c180d5bc89 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -3000,8 +3000,4 @@ namespace ts { } export function assertTypeIsNever(_: never): void { } // tslint:disable-line no-empty - - export function getBoundFunction(method: T | undefined, methodOf: {}): T | undefined { - return method && method.bind(methodOf); - } } diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 43211313237..ed5284e696c 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -144,17 +144,16 @@ namespace ts { enableStatistics(compilerOptions); const program = createProgram(rootFileNames, compilerOptions, compilerHost); - const exitStatus = compileProgram(program); - + const exitStatus = emitFilesAndReportErrors(program, reportDiagnostic, s => sys.write(s + sys.newLine)); reportStatistics(program); return sys.exit(exitStatus); } function updateWatchCompilationHost(watchCompilerHost: WatchCompilerHost) { - const compilerWithBuilderState = watchCompilerHost.afterProgramCreate; + const compileUsingBuilder = watchCompilerHost.afterProgramCreate; watchCompilerHost.beforeProgramCreate = enableStatistics; - watchCompilerHost.afterProgramCreate = (host, program) => { - compilerWithBuilderState(host, program); + watchCompilerHost.afterProgramCreate = program => { + compileUsingBuilder(program); reportStatistics(program); }; } @@ -175,40 +174,6 @@ namespace ts { createWatch(watchCompilerHost); } - function compileProgram(program: Program): ExitStatus { - let diagnostics: Diagnostic[]; - - // First get and report any syntactic errors. - diagnostics = program.getSyntacticDiagnostics().slice(); - - // If we didn't have any syntactic errors, then also try getting the global and - // semantic errors. - if (diagnostics.length === 0) { - diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics()); - - if (diagnostics.length === 0) { - diagnostics = program.getSemanticDiagnostics().slice(); - } - } - - // Emit and report any errors we ran into. - const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit(); - addRange(diagnostics, emitDiagnostics); - - sortAndDeduplicateDiagnostics(diagnostics).forEach(reportDiagnostic); - writeFileAndEmittedFileList(sys, program, emittedFiles); - if (emitSkipped && diagnostics.length > 0) { - // If the emitter didn't emit anything, then pass that value along. - return ExitStatus.DiagnosticsPresent_OutputsSkipped; - } - else if (diagnostics.length > 0) { - // The emitter emitted something, inform the caller if that happened in the presence - // of diagnostics or not. - return ExitStatus.DiagnosticsPresent_OutputsGenerated; - } - return ExitStatus.Success; - } - function enableStatistics(compilerOptions: CompilerOptions) { if (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics) { performance.enable(); diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 67caf3c79ca..fcee17e0328 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -78,30 +78,152 @@ namespace ts { return configParseResult; } + /** + * Program structure needed to emit the files and report diagnostics + */ + export interface ProgramToEmitFilesAndReportErrors { + getCurrentDirectory(): string; + getCompilerOptions(): CompilerOptions; + getSourceFiles(): ReadonlyArray; + getSyntacticDiagnostics(): ReadonlyArray; + getOptionsDiagnostics(): ReadonlyArray; + getGlobalDiagnostics(): ReadonlyArray; + getSemanticDiagnostics(): ReadonlyArray; + emit(): EmitResult; + } + + /** + * Helper that emit files, report diagnostics and lists emitted and/or source files depending on compiler options + */ + export function emitFilesAndReportErrors(program: ProgramToEmitFilesAndReportErrors, reportDiagnostic: DiagnosticReporter, writeFileName?: (s: string) => void) { + // First get and report any syntactic errors. + const diagnostics = program.getSyntacticDiagnostics().slice(); + let reportSemanticDiagnostics = false; + + // If we didn't have any syntactic errors, then also try getting the global and + // semantic errors. + if (diagnostics.length === 0) { + addRange(diagnostics, program.getOptionsDiagnostics()); + addRange(diagnostics, program.getGlobalDiagnostics()); + + if (diagnostics.length === 0) { + reportSemanticDiagnostics = true; + } + } + + // Emit and report any errors we ran into. + const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit(); + addRange(diagnostics, emitDiagnostics); + + if (reportSemanticDiagnostics) { + addRange(diagnostics, program.getSemanticDiagnostics()); + } + + sortAndDeduplicateDiagnostics(diagnostics).forEach(reportDiagnostic); + if (writeFileName) { + const currentDir = program.getCurrentDirectory(); + forEach(emittedFiles, file => { + const filepath = getNormalizedAbsolutePath(file, currentDir); + writeFileName(`TSFILE: ${filepath}`); + }); + + if (program.getCompilerOptions().listFiles) { + forEach(program.getSourceFiles(), file => { + writeFileName(file.fileName); + }); + } + } + + if (emitSkipped && diagnostics.length > 0) { + // If the emitter didn't emit anything, then pass that value along. + return ExitStatus.DiagnosticsPresent_OutputsSkipped; + } + else if (diagnostics.length > 0) { + // The emitter emitted something, inform the caller if that happened in the presence + // of diagnostics or not. + return ExitStatus.DiagnosticsPresent_OutputsGenerated; + } + return ExitStatus.Success; + } + + /** + * Creates the function that emits files and reports errors when called with program + */ + function createEmitFilesAndReportErrorsWithBuilderUsingSystem(system: System, reportDiagnostic: DiagnosticReporter) { + const emitErrorsAndReportErrorsWithBuilder = createEmitFilesAndReportErrorsWithBuilder({ + useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, + createHash: system.createHash && (s => system.createHash(s)), + writeFile, + reportDiagnostic, + writeFileName: s => system.write(s + system.newLine) + }); + let host: CachedDirectoryStructureHost | undefined; + return { + emitFilesAndReportError: (program: Program) => emitErrorsAndReportErrorsWithBuilder(program), + setHost: (cachedDirectoryStructureHost: CachedDirectoryStructureHost) => host = cachedDirectoryStructureHost + }; + + function getHost() { + return host || system; + } + + function ensureDirectoriesExist(directoryPath: string) { + if (directoryPath.length > getRootLength(directoryPath) && !getHost().directoryExists(directoryPath)) { + const parentDirectory = getDirectoryPath(directoryPath); + ensureDirectoriesExist(parentDirectory); + getHost().createDirectory(directoryPath); + } + } + + function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, onError: (message: string) => void) { + try { + performance.mark("beforeIOWrite"); + ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); + + getHost().writeFile(fileName, text, writeByteOrderMark); + + performance.mark("afterIOWrite"); + performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); + } + catch (e) { + if (onError) { + onError(e.message); + } + } + } + } + + const noopFileWatcher: FileWatcher = { close: noop }; + /** * Creates the watch compiler host that can be extended with config file or root file names and options host */ - function createWatchCompilerHost(system = sys, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHost { - return { + function createWatchCompilerHost(system = sys, reportDiagnostic: DiagnosticReporter): WatchCompilerHost { + const { emitFilesAndReportError, setHost } = createEmitFilesAndReportErrorsWithBuilderUsingSystem(system, reportDiagnostic); + const host: WatchCompilerHost = { useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, getNewLine: () => system.newLine, - getCurrentDirectory: getBoundFunction(system.getCurrentDirectory, system), + getCurrentDirectory: () => system.getCurrentDirectory(), getDefaultLibLocation, getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), - fileExists: getBoundFunction(system.fileExists, system), - readFile: getBoundFunction(system.readFile, system), - directoryExists: getBoundFunction(system.directoryExists, system), - getDirectories: getBoundFunction(system.getDirectories, system), - readDirectory: getBoundFunction(system.readDirectory, system), - realpath: getBoundFunction(system.realpath, system), - watchFile: getBoundFunction(system.watchFile, system), - watchDirectory: getBoundFunction(system.watchDirectory, system), - setTimeout: getBoundFunction(system.setTimeout, system), - clearTimeout: getBoundFunction(system.clearTimeout, system), - trace: getBoundFunction(system.write, system), + fileExists: path => system.fileExists(path), + readFile: (path, encoding) => system.readFile(path, encoding), + directoryExists: path => system.directoryExists(path), + getDirectories: path => system.getDirectories(path), + readDirectory: (path, extensions, exclude, include, depth) => system.readDirectory(path, extensions, exclude, include, depth), + realpath: system.realpath && (path => system.realpath(path)), + watchFile: system.watchFile ? ((path, callback, pollingInterval) => system.watchFile(path, callback, pollingInterval)) : () => noopFileWatcher, + watchDirectory: system.watchDirectory ? ((path, callback, recursive) => system.watchDirectory(path, callback, recursive)) : () => noopFileWatcher, + setTimeout: system.setTimeout ? ((callback, ms, ...args: any[]) => system.setTimeout.call(system, callback, ms, ...args)) : noop, + clearTimeout: system.clearTimeout ? (timeoutId => system.clearTimeout(timeoutId)) : noop, + trace: s => system.write(s), onWatchStatusChange, - afterProgramCreate: createProgramCompilerWithBuilderState(system, reportDiagnostic) + createDirectory: path => system.createDirectory(path), + writeFile: (path, data, writeByteOrderMark) => system.writeFile(path, data, writeByteOrderMark), + onCachedDirectoryStructureHostCreate: host => setHost(host), + afterProgramCreate: emitFilesAndReportError, }; + return host; function getDefaultLibLocation() { return getDirectoryPath(normalizePath(system.getExecutingFilePath())); @@ -140,7 +262,7 @@ namespace ts { * Creates the watch compiler host from system for compiling root files and options in watch mode */ export function createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter | undefined): WatchCompilerHostOfFilesAndCompilerOptions { - const host = createWatchCompilerHost(system, reportDiagnostic) as WatchCompilerHostOfFilesAndCompilerOptions; + const host = createWatchCompilerHost(system, reportDiagnostic || createDiagnosticReporter(system)) as WatchCompilerHostOfFilesAndCompilerOptions; host.rootFiles = rootFiles; host.options = options; return host; @@ -150,99 +272,75 @@ namespace ts { namespace ts { export type DiagnosticReporter = (diagnostic: Diagnostic) => void; - /** - * Writes emitted files, source files depending on options - */ - /*@internal*/ - export function writeFileAndEmittedFileList(system: System, program: Program, emittedFiles: string[]) { - const currentDir = system.getCurrentDirectory(); - forEach(emittedFiles, file => { - const filepath = getNormalizedAbsolutePath(file, currentDir); - system.write(`TSFILE: ${filepath}${system.newLine}`); - }); + interface BuilderProgram extends ProgramToEmitFilesAndReportErrors { + updateProgram(program: Program): void; + } - if (program.getCompilerOptions().listFiles) { - forEach(program.getSourceFiles(), file => { - system.write(file.fileName + system.newLine); - }); + function createBuilderProgram(host: BuilderEmitHost): BuilderProgram { + const builder = createEmitAndSemanticDiagnosticsBuilder({ + getCanonicalFileName: createGetCanonicalFileName(host.useCaseSensitiveFileNames()), + computeHash: host.createHash ? host.createHash : identity + }); + let program: Program; + return { + getCurrentDirectory: () => program.getCurrentDirectory(), + getCompilerOptions: () => program.getCompilerOptions(), + getSourceFiles: () => program.getSourceFiles(), + getSyntacticDiagnostics: () => program.getSyntacticDiagnostics(), + getOptionsDiagnostics: () => program.getOptionsDiagnostics(), + getGlobalDiagnostics: () => program.getGlobalDiagnostics(), + getSemanticDiagnostics: () => builder.getSemanticDiagnostics(program), + emit, + updateProgram + }; + + function updateProgram(p: Program) { + program = p; + builder.updateProgram(p); + } + + function emit(): EmitResult { + // Emit and report any errors we ran into. + let sourceMaps: SourceMapData[]; + let emitSkipped: boolean; + let diagnostics: Diagnostic[]; + let emittedFiles: string[]; + + let affectedEmitResult: AffectedFileResult; + while (affectedEmitResult = builder.emitNextAffectedFile(program, host.writeFile)) { + emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped; + diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics); + emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles); + sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps); + } + return { + emitSkipped, + diagnostics, + emittedFiles, + sourceMaps + }; } } /** - * Creates the function that compiles the program by maintaining the builder for the program and reports the errors and emits files + * Host needed to emit files and report errors using builder */ - export function createProgramCompilerWithBuilderState(system = sys, reportDiagnostic?: DiagnosticReporter) { - reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); - const builder = createEmitAndSemanticDiagnosticsBuilder({ - getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), - computeHash: getBoundFunction(system.createHash, system) || identity - }); + export interface BuilderEmitHost { + useCaseSensitiveFileNames(): boolean; + createHash?: (data: string) => string; + writeFile: WriteFileCallback; + reportDiagnostic: DiagnosticReporter; + writeFileName?: (s: string) => void; + } - return (host: DirectoryStructureHost, program: Program) => { - builder.updateProgram(program); - - // First get and report any syntactic errors. - const diagnostics = program.getSyntacticDiagnostics().slice(); - let reportSemanticDiagnostics = false; - - // If we didn't have any syntactic errors, then also try getting the global and - // semantic errors. - if (diagnostics.length === 0) { - addRange(diagnostics, program.getOptionsDiagnostics()); - addRange(diagnostics, program.getGlobalDiagnostics()); - - if (diagnostics.length === 0) { - reportSemanticDiagnostics = true; - } - } - - // Emit and report any errors we ran into. - const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined; - let sourceMaps: SourceMapData[]; - let emitSkipped: boolean; - - let affectedEmitResult: AffectedFileResult; - while (affectedEmitResult = builder.emitNextAffectedFile(program, writeFile)) { - emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped; - addRange(diagnostics, affectedEmitResult.result.diagnostics); - sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps); - } - - if (reportSemanticDiagnostics) { - addRange(diagnostics, builder.getSemanticDiagnostics(program)); - } - - sortAndDeduplicateDiagnostics(diagnostics).forEach(reportDiagnostic); - writeFileAndEmittedFileList(system, program, emittedFiles); - - function ensureDirectoriesExist(directoryPath: string) { - if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists(directoryPath)) { - const parentDirectory = getDirectoryPath(directoryPath); - ensureDirectoriesExist(parentDirectory); - host.createDirectory(directoryPath); - } - } - - function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, onError: (message: string) => void) { - try { - performance.mark("beforeIOWrite"); - ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - - host.writeFile(fileName, text, writeByteOrderMark); - - performance.mark("afterIOWrite"); - performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); - - if (emittedFiles) { - emittedFiles.push(fileName); - } - } - catch (e) { - if (onError) { - onError(e.message); - } - } - } + /** + * Creates the function that reports the program errors and emit files every time it is called with argument as program + */ + export function createEmitFilesAndReportErrorsWithBuilder(host: BuilderEmitHost) { + const builderProgram = createBuilderProgram(host); + return (program: Program) => { + builderProgram.updateProgram(program); + emitFilesAndReportErrors(builderProgram, host.reportDiagnostic, host.writeFileName); }; } @@ -250,7 +348,7 @@ namespace ts { /** If provided, callback to invoke before each program creation */ beforeProgramCreate?(compilerOptions: CompilerOptions): void; /** If provided, callback to invoke after every new program creation */ - afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; + afterProgramCreate?(program: Program): void; /** If provided, called with Diagnostic message that informs about change in watch status */ onWatchStatusChange?(diagnostic: Diagnostic, newLine: string): void; @@ -297,6 +395,14 @@ namespace ts { clearTimeout?(timeoutId: any): void; } + /** Internal interface used to wire emit through same host */ + /*@internal*/ + export interface WatchCompilerHost { + createDirectory?(path: string): void; + writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void; + onCachedDirectoryStructureHostCreate?(host: CachedDirectoryStructureHost): void; + } + /** * Host to create watch with root files and options */ @@ -315,12 +421,12 @@ namespace ts { /** * Reports the diagnostics in reading/writing or parsing of the config file */ - onConfigFileDiagnostic(diagnostic: Diagnostic): void; + onConfigFileDiagnostic: DiagnosticReporter; /** * Reports unrecoverable error when parsing config file */ - onUnRecoverableConfigFileDiagnostic(diagnostic: Diagnostic): void; + onUnRecoverableConfigFileDiagnostic: DiagnosticReporter; } /** @@ -345,7 +451,6 @@ namespace ts { */ /*@internal*/ export interface WatchCompilerHostOfConfigFile extends WatchCompilerHost { - cachedDirectoryStructureHost?: CachedDirectoryStructureHost; rootFiles?: string[]; options?: CompilerOptions; optionsToExtend?: CompilerOptions; @@ -418,23 +523,23 @@ namespace ts { const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames(); const currentDirectory = host.getCurrentDirectory(); const getCurrentDirectory = () => currentDirectory; - const onConfigFileDiagnostic = getBoundFunction(host.onConfigFileDiagnostic, host); - const readFile = getBoundFunction(host.readFile, host); + const readFile: (path: string, encoding?: string) => string | undefined = (path, encoding) => host.readFile(path, encoding); const { configFileName, optionsToExtend: optionsToExtendForConfigFile = {} } = host; - const beforeProgramCreate = getBoundFunction(host.beforeProgramCreate, host) || noop; - const afterProgramCreate = getBoundFunction(host.afterProgramCreate, host) || noop; let { rootFiles: rootFileNames, options: compilerOptions, configFileSpecs, configFileWildCardDirectories } = host; const cachedDirectoryStructureHost = configFileName && createCachedDirectoryStructureHost(host, currentDirectory, useCaseSensitiveFileNames); + if (cachedDirectoryStructureHost && host.onCachedDirectoryStructureHostCreate) { + host.onCachedDirectoryStructureHostCreate(cachedDirectoryStructureHost); + } const directoryStructureHost: DirectoryStructureHost = cachedDirectoryStructureHost || host; const parseConfigFileHost: ParseConfigFileHost = { useCaseSensitiveFileNames, - readDirectory: getBoundFunction(directoryStructureHost.readDirectory, directoryStructureHost), - fileExists: getBoundFunction(directoryStructureHost.fileExists, directoryStructureHost), + readDirectory: (path, extensions, exclude, include, depth) => directoryStructureHost.readDirectory(path, extensions, exclude, include, depth), + fileExists: path => host.fileExists(path), readFile, getCurrentDirectory, - onConfigFileDiagnostic, - onUnRecoverableConfigFileDiagnostic: getBoundFunction(host.onUnRecoverableConfigFileDiagnostic, host) + onConfigFileDiagnostic: host.onConfigFileDiagnostic, + onUnRecoverableConfigFileDiagnostic: host.onUnRecoverableConfigFileDiagnostic }; // From tsc we want to get already parsed result and hence check for rootFileNames @@ -460,8 +565,8 @@ namespace ts { // Members for CompilerHost getSourceFile: (fileName, languageVersion, onError?, shouldCreateNewSourceFile?) => getVersionedSourceFileByPath(fileName, toPath(fileName), languageVersion, onError, shouldCreateNewSourceFile), getSourceFileByPath: getVersionedSourceFileByPath, - getDefaultLibLocation: getBoundFunction(host.getDefaultLibLocation, host), - getDefaultLibFileName: getBoundFunction(host.getDefaultLibFileName, host), + getDefaultLibLocation: host.getDefaultLibLocation && (() => host.getDefaultLibLocation()), + getDefaultLibFileName: options => host.getDefaultLibFileName(options), writeFile: notImplemented, getCurrentDirectory, useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, @@ -470,9 +575,9 @@ namespace ts { fileExists, readFile, trace, - directoryExists: getBoundFunction(directoryStructureHost.directoryExists, directoryStructureHost), - getDirectories: getBoundFunction(directoryStructureHost.getDirectories, directoryStructureHost), - realpath: getBoundFunction(host.realpath, host), + directoryExists: directoryStructureHost.directoryExists && (path => directoryStructureHost.directoryExists(path)), + getDirectories: directoryStructureHost.getDirectories && (path => directoryStructureHost.getDirectories(path)), + realpath: host.realpath && (s => host.realpath(s)), onReleaseOldSourceFile, // Members for ResolutionCacheHost toPath, @@ -495,8 +600,8 @@ namespace ts { ); // Resolve module using host module resolution strategy if provided otherwise use resolution cache to resolve module names compilerHost.resolveModuleNames = host.resolveModuleNames ? - host.resolveModuleNames.bind(host) : - resolutionCache.resolveModuleNames.bind(resolutionCache); + ((moduleNames, containingFile, reusedNames) => host.resolveModuleNames(moduleNames, containingFile, reusedNames)) : + ((moduleNames, containingFile, reusedNames) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames)); compilerHost.resolveTypeReferenceDirectives = resolutionCache.resolveTypeReferenceDirectives.bind(resolutionCache); reportWatchDiagnostic(Diagnostics.Starting_compilation_in_watch_mode); @@ -524,7 +629,9 @@ namespace ts { return program; } - beforeProgramCreate(compilerOptions); + if (host.beforeProgramCreate) { + host.beforeProgramCreate(compilerOptions); + } // Compile the program const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !program; @@ -555,7 +662,9 @@ namespace ts { missingFilePathsRequestedForRelease = undefined; } - afterProgramCreate(directoryStructureHost, program); + if (host.afterProgramCreate) { + host.afterProgramCreate(program); + } reportWatchDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes); return program; } @@ -722,7 +831,7 @@ namespace ts { function reloadFileNamesFromConfigFile() { const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, parseConfigFileHost); if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) { - onConfigFileDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); + host.onConfigFileDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); } rootFileNames = result.fileNames; diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index bf5a42b7b08..24c9737fb26 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -49,12 +49,12 @@ namespace ts { return { useCaseSensitiveFileNames, fileExists, - readFile: getBoundFunction(host.readFile, host), + readFile: (path, encoding) => host.readFile(path, encoding), directoryExists: host.directoryExists && directoryExists, getDirectories, readDirectory, - createDirectory, - writeFile, + createDirectory: host.createDirectory && createDirectory, + writeFile: host.writeFile && writeFile, addOrDeleteFileOrDirectory, addOrDeleteFile, clearCache diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index bafe90a59cb..6afeae16ef5 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1765,11 +1765,11 @@ namespace ts.server { return this.getOrCreateScriptInfoWorker(fileName, currentDirectory, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent); } - getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) { + getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: { fileExists(path: string): boolean; }) { return this.getOrCreateScriptInfoWorker(fileName, this.currentDirectory, openedByClient, fileContent, scriptKind, hasMixedContent, hostToQueryFileExistsOn); } - private getOrCreateScriptInfoWorker(fileName: NormalizedPath, currentDirectory: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) { + private getOrCreateScriptInfoWorker(fileName: NormalizedPath, currentDirectory: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: { fileExists(path: string): boolean; }) { Debug.assert(fileContent === undefined || openedByClient, "ScriptInfo needs to be opened by client to be able to set its user defined content"); const path = normalizedPathToPath(fileName, currentDirectory, this.toCanonicalFileName); let info = this.getScriptInfoForPath(path); diff --git a/src/server/project.ts b/src/server/project.ts index 9ae1b1d0b6e..26d8aba0bd5 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -201,6 +201,9 @@ namespace ts.server { /*@internal*/ readonly currentDirectory: string; + /*@internal*/ + public directoryStructureHost: DirectoryStructureHost; + /*@internal*/ constructor( /*@internal*/readonly projectName: string, @@ -211,8 +214,9 @@ namespace ts.server { languageServiceEnabled: boolean, private compilerOptions: CompilerOptions, public compileOnSaveEnabled: boolean, - /*@internal*/public directoryStructureHost: DirectoryStructureHost, + directoryStructureHost: DirectoryStructureHost, currentDirectory: string | undefined) { + this.directoryStructureHost = directoryStructureHost; this.currentDirectory = this.projectService.getNormalizedAbsolutePath(currentDirectory || ""); this.cancellationToken = new ThrottledCancellationToken(this.projectService.cancellationToken, this.projectService.throttleWaitMilliseconds); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index ca399847583..4abb45a11e4 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7270,7 +7270,6 @@ declare namespace ts.server { private documentRegistry; private compilerOptions; compileOnSaveEnabled: boolean; - directoryStructureHost: DirectoryStructureHost; private rootFiles; private rootFilesMap; private program; @@ -7741,7 +7740,9 @@ declare namespace ts.server { getScriptInfo(uncheckedFileName: string): ScriptInfo; private watchClosedScriptInfo(info); private stopWatchingScriptInfo(info); - getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost): ScriptInfo; + getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: { + fileExists(path: string): boolean; + }): ScriptInfo; private getOrCreateScriptInfoWorker(fileName, currentDirectory, openedByClient, fileContent?, scriptKind?, hasMixedContent?, hostToQueryFileExistsOn?); /** * This gets the script info for the normalized path. If the path is not rooted disk path then the open script info with project root context is preferred diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 521fd1e0ad6..eed0c7cc5d8 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3811,14 +3811,24 @@ declare namespace ts { declare namespace ts { type DiagnosticReporter = (diagnostic: Diagnostic) => void; /** - * Creates the function that compiles the program by maintaining the builder for the program and reports the errors and emits files + * Host needed to emit files and report errors using builder */ - function createProgramCompilerWithBuilderState(system?: System, reportDiagnostic?: DiagnosticReporter): (host: DirectoryStructureHost, program: Program) => void; + interface BuilderEmitHost { + useCaseSensitiveFileNames(): boolean; + createHash?: (data: string) => string; + writeFile: WriteFileCallback; + reportDiagnostic: DiagnosticReporter; + writeFileName?: (s: string) => void; + } + /** + * Creates the function that reports the program errors and emit files every time it is called with argument as program + */ + function createEmitFilesAndReportErrorsWithBuilder(host: BuilderEmitHost): (program: Program) => void; interface WatchCompilerHost { /** If provided, callback to invoke before each program creation */ beforeProgramCreate?(compilerOptions: CompilerOptions): void; /** If provided, callback to invoke after every new program creation */ - afterProgramCreate?(host: DirectoryStructureHost, program: Program): void; + afterProgramCreate?(program: Program): void; /** If provided, called with Diagnostic message that informs about change in watch status */ onWatchStatusChange?(diagnostic: Diagnostic, newLine: string): void; useCaseSensitiveFileNames(): boolean; @@ -3852,7 +3862,7 @@ declare namespace ts { watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; - /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together*/ + /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */ setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; /** If provided, will be used to reset existing delayed compilation */ clearTimeout?(timeoutId: any): void; @@ -3873,11 +3883,11 @@ declare namespace ts { /** * Reports the diagnostics in reading/writing or parsing of the config file */ - onConfigFileDiagnostic(diagnostic: Diagnostic): void; + onConfigFileDiagnostic: DiagnosticReporter; /** * Reports unrecoverable error when parsing config file */ - onUnRecoverableConfigFileDiagnostic(diagnostic: Diagnostic): void; + onUnRecoverableConfigFileDiagnostic: DiagnosticReporter; } /** * Host to create watch with config file From c1cbf588ff96721ce71fb6689358f07fb43cc2b8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 6 Dec 2017 12:27:46 -0800 Subject: [PATCH 031/172] Update the project graph before checking if opened file is present in the existing project Fixes #20017 --- .../unittests/tsserverProjectSystem.ts | 32 +++++++++++++++++++ src/server/editorServices.ts | 26 +++++++++------ .../reference/api/tsserverlibrary.d.ts | 2 +- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 39dde23b28a..9d2566b7b0b 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -3468,6 +3468,38 @@ namespace ts.projectSystem { it("works when project root is used with case-insensitive system", () => { verifyOpenFileWorks(/*useCaseSensitiveFileNames*/ false); }); + + it("uses existing project even if project refresh is pending", () => { + const projectFolder = "/user/someuser/projects/myproject"; + const aFile: FileOrFolder = { + path: `${projectFolder}/src/a.ts`, + content: "export const x = 0;" + }; + const configFile: FileOrFolder = { + path: `${projectFolder}/tsconfig.json`, + content: "{}" + }; + const files = [aFile, configFile, libFile]; + const host = createServerHost(files); + const service = createProjectService(host); + service.openClientFile(aFile.path, /*fileContent*/ undefined, ScriptKind.TS, projectFolder); + verifyProject(); + + const bFile: FileOrFolder = { + path: `${projectFolder}/src/b.ts`, + content: `export {}; declare module "./a" { export const y: number; }` + }; + files.push(bFile); + host.reloadFS(files); + service.openClientFile(bFile.path, /*fileContent*/ undefined, ScriptKind.TS, projectFolder); + verifyProject(); + + function verifyProject() { + assert.isDefined(service.configuredProjects.get(configFile.path)); + const project = service.configuredProjects.get(configFile.path); + checkProjectActualFiles(project, files.map(f => f.path)); + } + }); }); describe("tsserverProjectSystem Language service", () => { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index a5b09c6a63f..c4446c049b6 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -725,15 +725,6 @@ namespace ts.server { } } - private findContainingExternalProject(fileName: NormalizedPath): ExternalProject { - for (const proj of this.externalProjects) { - if (proj.containsFile(fileName)) { - return proj; - } - } - return undefined; - } - getFormatCodeOptions(file?: NormalizedPath) { let formatCodeSettings: FormatCodeSettings; if (file) { @@ -1991,13 +1982,24 @@ namespace ts.server { return this.openClientFileWithNormalizedPath(toNormalizedPath(fileName), fileContent, scriptKind, /*hasMixedContent*/ false, projectRootPath ? toNormalizedPath(projectRootPath) : undefined); } + private findExternalProjetContainingOpenScriptInfo(info: ScriptInfo): ExternalProject { + for (const proj of this.externalProjects) { + // Ensure project structure is uptodate to check if info is present in external project + proj.updateGraph(); + if (proj.containsScriptInfo(info)) { + return proj; + } + } + return undefined; + } + openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult { let configFileName: NormalizedPath; let sendConfigFileDiagEvent = false; let configFileErrors: ReadonlyArray; const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, projectRootPath ? this.getNormalizedAbsolutePath(projectRootPath) : this.currentDirectory, fileContent, scriptKind, hasMixedContent); - let project: ConfiguredProject | ExternalProject = this.findContainingExternalProject(fileName); + let project: ConfiguredProject | ExternalProject = this.findExternalProjetContainingOpenScriptInfo(info); if (!project) { configFileName = this.getConfigFileNameForFile(info, projectRootPath); if (configFileName) { @@ -2007,6 +2009,10 @@ namespace ts.server { // Send the event only if the project got created as part of this open request sendConfigFileDiagEvent = true; } + else { + // Ensure project is ready to check if it contains opened script info + project.updateGraph(); + } } } if (project && !project.languageServiceEnabled) { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index abd03c26b0b..8ae40d84afd 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7605,7 +7605,6 @@ declare namespace ts.server { * @param forceInferredProjectsRefresh when true updates the inferred projects even if there is no pending work to update the files/project structures */ private ensureProjectStructuresUptoDate(forceInferredProjectsRefresh?); - private findContainingExternalProject(fileName); getFormatCodeOptions(file?: NormalizedPath): FormatCodeSettings; private updateProjectGraphs(projects); private onSourceFileChanged(fileName, eventKind); @@ -7723,6 +7722,7 @@ declare namespace ts.server { * @param fileContent is a known version of the file content that is more up to date than the one on disk */ openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind, projectRootPath?: string): OpenConfiguredProjectResult; + private findExternalProjetContainingOpenScriptInfo(info); openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult; /** * Close file whose contents is managed by the client From a21b07405570dae526eb4f54e9bbd714d0a3b4cd Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 6 Dec 2017 13:59:53 -0800 Subject: [PATCH 032/172] Update the builder to take options aligning with the WatchCompilerHost --- src/compiler/builder.ts | 25 +++++++++++++++---- src/compiler/watch.ts | 9 ++----- src/harness/unittests/builder.ts | 10 ++------ src/server/project.ts | 4 +-- .../reference/api/tsserverlibrary.d.ts | 10 ++++++-- tests/baselines/reference/api/typescript.d.ts | 14 +++++++---- 6 files changed, 43 insertions(+), 29 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index ddbb015a2ee..c25cbb0d5a4 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -66,6 +66,15 @@ namespace ts { export function createBuilder(options: BuilderOptions, builderType: BuilderType.SemanticDiagnosticsBuilder): SemanticDiagnosticsBuilder; export function createBuilder(options: BuilderOptions, builderType: BuilderType.EmitAndSemanticDiagnosticsBuilder): EmitAndSemanticDiagnosticsBuilder; export function createBuilder(options: BuilderOptions, builderType: BuilderType) { + /** + * Create the canonical file name for identity + */ + const getCanonicalFileName = createGetCanonicalFileName(options.useCaseSensitiveFileNames()); + /** + * Computing hash to for signature verification + */ + const computeHash = options.createHash || identity; + /** * Information of the file eg. its version, signature etc */ @@ -572,7 +581,7 @@ namespace ts { else { const emitOutput = getFileEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken); if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { - latestSignature = options.computeHash(emitOutput.outputFiles[0].text); + latestSignature = computeHash(emitOutput.outputFiles[0].text); } else { latestSignature = prevSignature; @@ -609,7 +618,7 @@ namespace ts { // Handle triple slash references if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { for (const referencedFile of sourceFile.referencedFiles) { - const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, options.getCanonicalFileName); + const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); addReferencedFile(referencedPath); } } @@ -622,7 +631,7 @@ namespace ts { } const fileName = resolvedTypeReferenceDirective.resolvedFileName; - const typeFilePath = toPath(fileName, sourceFileDirectory, options.getCanonicalFileName); + const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName); addReferencedFile(typeFilePath); }); } @@ -738,8 +747,14 @@ namespace ts { export type AffectedFileResult = { result: T; affected: SourceFile | Program; } | undefined; export interface BuilderOptions { - getCanonicalFileName: (fileName: string) => string; - computeHash: (data: string) => string; + /** + * return true if file names are treated with case sensitivity + */ + useCaseSensitiveFileNames(): boolean; + /** + * If provided this would be used this hash instead of actual file shape text for detecting changes + */ + createHash?: (data: string) => string; } /** diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index fcee17e0328..6770c036b10 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -277,10 +277,7 @@ namespace ts { } function createBuilderProgram(host: BuilderEmitHost): BuilderProgram { - const builder = createEmitAndSemanticDiagnosticsBuilder({ - getCanonicalFileName: createGetCanonicalFileName(host.useCaseSensitiveFileNames()), - computeHash: host.createHash ? host.createHash : identity - }); + const builder = createEmitAndSemanticDiagnosticsBuilder(host); let program: Program; return { getCurrentDirectory: () => program.getCurrentDirectory(), @@ -325,9 +322,7 @@ namespace ts { /** * Host needed to emit files and report errors using builder */ - export interface BuilderEmitHost { - useCaseSensitiveFileNames(): boolean; - createHash?: (data: string) => string; + export interface BuilderEmitHost extends BuilderOptions { writeFile: WriteFileCallback; reportDiagnostic: DiagnosticReporter; writeFileName?: (s: string) => void; diff --git a/src/harness/unittests/builder.ts b/src/harness/unittests/builder.ts index 8ad7f4da3f9..a6353d9c0c3 100644 --- a/src/harness/unittests/builder.ts +++ b/src/harness/unittests/builder.ts @@ -81,10 +81,7 @@ namespace ts { }); function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray) => void { - const builder = createEmitAndSemanticDiagnosticsBuilder({ - getCanonicalFileName: identity, - computeHash: identity - }); + const builder = createEmitAndSemanticDiagnosticsBuilder({ useCaseSensitiveFileNames: returnTrue, }); return fileNames => { const program = getProgram(); builder.updateProgram(program); @@ -97,10 +94,7 @@ namespace ts { } function makeAssertChangesWithCancellationToken(getProgram: () => Program): (fileNames: ReadonlyArray, cancelAfterEmitLength?: number) => void { - const builder = createEmitAndSemanticDiagnosticsBuilder({ - getCanonicalFileName: identity, - computeHash: identity - }); + const builder = createEmitAndSemanticDiagnosticsBuilder({ useCaseSensitiveFileNames: returnTrue, }); let cancel = false; const cancellationToken: CancellationToken = { isCancellationRequested: () => cancel, diff --git a/src/server/project.ts b/src/server/project.ts index 26d8aba0bd5..1fa12e50120 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -462,8 +462,8 @@ namespace ts.server { this.updateGraph(); if (!this.builder) { this.builder = createInternalBuilder({ - getCanonicalFileName: this.projectService.toCanonicalFileName, - computeHash: data => this.projectService.host.createHash(data) + useCaseSensitiveFileNames: () => this.useCaseSensitiveFileNames(), + createHash: data => this.projectService.host.createHash(data) }); } this.builder.updateProgram(this.program); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 4abb45a11e4..c613293c9ba 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3772,8 +3772,14 @@ declare namespace ts { affected: SourceFile | Program; } | undefined; interface BuilderOptions { - getCanonicalFileName: (fileName: string) => string; - computeHash: (data: string) => string; + /** + * return true if file names are treated with case sensitivity + */ + useCaseSensitiveFileNames(): boolean; + /** + * If provided this would be used this hash instead of actual file shape text for detecting changes + */ + createHash?: (data: string) => string; } /** * Builder to manage the program state changes diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index eed0c7cc5d8..c9d4e3dd0a3 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3719,8 +3719,14 @@ declare namespace ts { affected: SourceFile | Program; } | undefined; interface BuilderOptions { - getCanonicalFileName: (fileName: string) => string; - computeHash: (data: string) => string; + /** + * return true if file names are treated with case sensitivity + */ + useCaseSensitiveFileNames(): boolean; + /** + * If provided this would be used this hash instead of actual file shape text for detecting changes + */ + createHash?: (data: string) => string; } /** * Builder to manage the program state changes @@ -3813,9 +3819,7 @@ declare namespace ts { /** * Host needed to emit files and report errors using builder */ - interface BuilderEmitHost { - useCaseSensitiveFileNames(): boolean; - createHash?: (data: string) => string; + interface BuilderEmitHost extends BuilderOptions { writeFile: WriteFileCallback; reportDiagnostic: DiagnosticReporter; writeFileName?: (s: string) => void; From 39bf33d8414b56a5642644e0086dd63bd67df477 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Dec 2017 10:02:02 -0800 Subject: [PATCH 033/172] Few renames --- src/compiler/builder.ts | 42 +++++++++---------- src/compiler/watch.ts | 2 +- .../reference/api/tsserverlibrary.d.ts | 6 +-- tests/baselines/reference/api/typescript.d.ts | 8 ++-- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index c25cbb0d5a4..9c7707bd1d8 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -28,14 +28,14 @@ namespace ts { /** * Create the internal builder to get files affected by sourceFile */ - export function createInternalBuilder(options: BuilderOptions): InternalBuilder { - return createBuilder(options, BuilderType.InternalBuilder); + export function createInternalBuilder(host: BuilderHost): InternalBuilder { + return createBuilder(host, BuilderKind.BuilderKindInternal); } - export enum BuilderType { - InternalBuilder, - SemanticDiagnosticsBuilder, - EmitAndSemanticDiagnosticsBuilder + export enum BuilderKind { + BuilderKindInternal, + BuilderKindSemanticDiagnostics, + BuilderKindEmitAndSemanticDiagnostics } /** @@ -62,18 +62,18 @@ namespace ts { return map1.size === map2.size && !forEachEntry(map1, (_value, key) => !map2.has(key)); } - export function createBuilder(options: BuilderOptions, builderType: BuilderType.InternalBuilder): InternalBuilder; - export function createBuilder(options: BuilderOptions, builderType: BuilderType.SemanticDiagnosticsBuilder): SemanticDiagnosticsBuilder; - export function createBuilder(options: BuilderOptions, builderType: BuilderType.EmitAndSemanticDiagnosticsBuilder): EmitAndSemanticDiagnosticsBuilder; - export function createBuilder(options: BuilderOptions, builderType: BuilderType) { + export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindInternal): InternalBuilder; + export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindSemanticDiagnostics): SemanticDiagnosticsBuilder; + export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindEmitAndSemanticDiagnostics): EmitAndSemanticDiagnosticsBuilder; + export function createBuilder(host: BuilderHost, builderKind: BuilderKind) { /** * Create the canonical file name for identity */ - const getCanonicalFileName = createGetCanonicalFileName(options.useCaseSensitiveFileNames()); + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); /** * Computing hash to for signature verification */ - const computeHash = options.createHash || identity; + const computeHash = host.createHash || identity; /** * Information of the file eg. its version, signature etc @@ -141,12 +141,12 @@ namespace ts { */ const seenAffectedFiles = createMap(); - switch (builderType) { - case BuilderType.InternalBuilder: + switch (builderKind) { + case BuilderKind.BuilderKindInternal: return getInternalBuilder(); - case BuilderType.SemanticDiagnosticsBuilder: + case BuilderKind.BuilderKindSemanticDiagnostics: return getSemanticDiagnosticsBuilder(); - case BuilderType.EmitAndSemanticDiagnosticsBuilder: + case BuilderKind.BuilderKindEmitAndSemanticDiagnostics: return getEmitAndSemanticDiagnosticsBuilder(); default: notImplemented(); @@ -746,7 +746,7 @@ namespace ts { export type AffectedFileResult = { result: T; affected: SourceFile | Program; } | undefined; - export interface BuilderOptions { + export interface BuilderHost { /** * return true if file names are treated with case sensitivity */ @@ -813,15 +813,15 @@ namespace ts { /** * Create the builder to manage semantic diagnostics and cache them */ - export function createSemanticDiagnosticsBuilder(options: BuilderOptions): SemanticDiagnosticsBuilder { - return createBuilder(options, BuilderType.SemanticDiagnosticsBuilder); + export function createSemanticDiagnosticsBuilder(host: BuilderHost): SemanticDiagnosticsBuilder { + return createBuilder(host, BuilderKind.BuilderKindSemanticDiagnostics); } /** * Create the builder that can handle the changes in program and iterate through changed files * to emit the those files and manage semantic diagnostics cache as well */ - export function createEmitAndSemanticDiagnosticsBuilder(options: BuilderOptions): EmitAndSemanticDiagnosticsBuilder { - return createBuilder(options, BuilderType.EmitAndSemanticDiagnosticsBuilder); + export function createEmitAndSemanticDiagnosticsBuilder(host: BuilderHost): EmitAndSemanticDiagnosticsBuilder { + return createBuilder(host, BuilderKind.BuilderKindEmitAndSemanticDiagnostics); } } diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 6770c036b10..1c5a0ad5d99 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -322,7 +322,7 @@ namespace ts { /** * Host needed to emit files and report errors using builder */ - export interface BuilderEmitHost extends BuilderOptions { + export interface BuilderEmitHost extends BuilderHost { writeFile: WriteFileCallback; reportDiagnostic: DiagnosticReporter; writeFileName?: (s: string) => void; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 97a24adb74d..d7f3821a25d 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3771,7 +3771,7 @@ declare namespace ts { result: T; affected: SourceFile | Program; } | undefined; - interface BuilderOptions { + interface BuilderHost { /** * return true if file names are treated with case sensitivity */ @@ -3831,12 +3831,12 @@ declare namespace ts { /** * Create the builder to manage semantic diagnostics and cache them */ - function createSemanticDiagnosticsBuilder(options: BuilderOptions): SemanticDiagnosticsBuilder; + function createSemanticDiagnosticsBuilder(host: BuilderHost): SemanticDiagnosticsBuilder; /** * Create the builder that can handle the changes in program and iterate through changed files * to emit the those files and manage semantic diagnostics cache as well */ - function createEmitAndSemanticDiagnosticsBuilder(options: BuilderOptions): EmitAndSemanticDiagnosticsBuilder; + function createEmitAndSemanticDiagnosticsBuilder(host: BuilderHost): EmitAndSemanticDiagnosticsBuilder; } declare namespace ts { function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index c9d4e3dd0a3..06c75e843c0 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3718,7 +3718,7 @@ declare namespace ts { result: T; affected: SourceFile | Program; } | undefined; - interface BuilderOptions { + interface BuilderHost { /** * return true if file names are treated with case sensitivity */ @@ -3778,12 +3778,12 @@ declare namespace ts { /** * Create the builder to manage semantic diagnostics and cache them */ - function createSemanticDiagnosticsBuilder(options: BuilderOptions): SemanticDiagnosticsBuilder; + function createSemanticDiagnosticsBuilder(host: BuilderHost): SemanticDiagnosticsBuilder; /** * Create the builder that can handle the changes in program and iterate through changed files * to emit the those files and manage semantic diagnostics cache as well */ - function createEmitAndSemanticDiagnosticsBuilder(options: BuilderOptions): EmitAndSemanticDiagnosticsBuilder; + function createEmitAndSemanticDiagnosticsBuilder(host: BuilderHost): EmitAndSemanticDiagnosticsBuilder; } declare namespace ts { function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; @@ -3819,7 +3819,7 @@ declare namespace ts { /** * Host needed to emit files and report errors using builder */ - interface BuilderEmitHost extends BuilderOptions { + interface BuilderEmitHost extends BuilderHost { writeFile: WriteFileCallback; reportDiagnostic: DiagnosticReporter; writeFileName?: (s: string) => void; From 4c21cbf145b3dab90c6e5c6b6f2642125ce7b312 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Dec 2017 11:47:49 -0800 Subject: [PATCH 034/172] Create builderState so that when FilesAffectedBy is only api needed, we arent tracking changed files --- src/compiler/builderState.ts | 431 +++++++++++++++++++++++++++++++++++ src/compiler/program.ts | 1 - src/server/project.ts | 14 +- 3 files changed, 440 insertions(+), 6 deletions(-) create mode 100644 src/compiler/builderState.ts diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts new file mode 100644 index 00000000000..f6706195a75 --- /dev/null +++ b/src/compiler/builderState.ts @@ -0,0 +1,431 @@ +/// +namespace ts { + export interface EmitOutput { + outputFiles: OutputFile[]; + emitSkipped: boolean; + } + + export interface OutputFile { + name: string; + writeByteOrderMark: boolean; + text: string; + } +} + +/*@internal*/ +namespace ts { + export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, + cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput { + const outputFiles: OutputFile[] = []; + const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); + return { outputFiles, emitSkipped: emitResult.emitSkipped }; + + function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) { + outputFiles.push({ name: fileName, writeByteOrderMark, text }); + } + } + + /** + * Information about the source file: Its version and optional signature from last emit + */ + interface FileInfo { + version: string; + signature?: string; + } + + /** + * Referenced files with values for the keys as referenced file's path to be true + */ + type ReferencedSet = ReadonlyMap; + + function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined) { + if (map1 === undefined) { + return map2 === undefined; + } + if (map2 === undefined) { + return map1 === undefined; + } + // Has same size and every key is present in both maps + return map1.size === map2.size && !forEachEntry(map1, (_value, key) => !map2.has(key)); + } + + export interface BuilderStateHost { + /** + * true if file names are treated with case sensitivity + */ + useCaseSensitiveFileNames: boolean; + /** + * if provided this would be used this hash instead of actual file shape text for detecting changes + */ + createHash?: (data: string) => string; + /** + * Called when programState is initialized, indicating if isModuleEmit is changed + */ + onUpdateProgramInitialized(isModuleEmitChanged: boolean): void; + onSourceFileAdd(path: Path): void; + onSourceFileChanged(path: Path): void; + onSourceFileRemoved(path: Path): void; + } + + export interface BuilderState { + /** + * Updates the program in the builder to represent new state + */ + updateProgram(newProgram: Program): void; + /** + * Gets the files affected by the file path + */ + getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken, cacheToUpdateSignature?: Map): ReadonlyArray; + } + + export function createBuilderState(host: BuilderStateHost): BuilderState { + /** + * Create the canonical file name for identity + */ + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + /** + * Computing hash to for signature verification + */ + const computeHash = host.createHash || identity; + + /** + * Information of the file eg. its version, signature etc + */ + const fileInfos = createMap(); + + /** + * true if module emit is enabled + */ + let isModuleEmit: boolean; + + /** + * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled + * Otherwise undefined + */ + let referencedMap: Map | undefined; + + /** + * Get the files affected by the source file. + * This is dependent on whether its a module emit or not and hence function expression + */ + let getEmitDependentFilesAffectedBy: (programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) => ReadonlyArray; + + /** + * Map of files that have already called update signature. + * That means hence forth these files are assumed to have + * no change in their signature for this version of the program + */ + const hasCalledUpdateShapeSignature = createMap(); + + /** + * Cache of all files excluding default library file for the current program + */ + let allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; + + return { + updateProgram, + getFilesAffectedBy, + }; + + /** + * Update current state to reflect new program + * Updates changed files, references, file infos etc + */ + function updateProgram(newProgram: Program) { + const newProgramHasModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None; + const oldReferencedMap = referencedMap; + const isModuleEmitChanged = isModuleEmit !== newProgramHasModuleEmit; + if (isModuleEmitChanged) { + // Changes in the module emit, clear out everything and initialize as if first time + + // Clear file information + fileInfos.clear(); + + // Update the reference map creation + referencedMap = newProgramHasModuleEmit ? createMap() : undefined; + + // Update the module emit + isModuleEmit = newProgramHasModuleEmit; + getEmitDependentFilesAffectedBy = isModuleEmit ? + getFilesAffectedByUpdatedShapeWhenModuleEmit : + getFilesAffectedByUpdatedShapeWhenNonModuleEmit; + } + host.onUpdateProgramInitialized(isModuleEmitChanged); + + // Clear datas that cant be retained beyond previous state + hasCalledUpdateShapeSignature.clear(); + allFilesExcludingDefaultLibraryFile = undefined; + + // Create the reference map and update changed files + for (const sourceFile of newProgram.getSourceFiles()) { + const version = sourceFile.version; + const newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile); + const oldInfo = fileInfos.get(sourceFile.path); + let oldReferences: ReferencedSet; + + // Register changed file if its new file or we arent reusing old state + if (!oldInfo) { + // New file: Set the file info + fileInfos.set(sourceFile.path, { version }); + host.onSourceFileAdd(sourceFile.path); + } + // versions dont match + else if (oldInfo.version !== version || + // Referenced files changed + !hasSameKeys(newReferences, (oldReferences = oldReferencedMap && oldReferencedMap.get(sourceFile.path))) || + // Referenced file was deleted in the new program + newReferences && forEachEntry(newReferences, (_value, path) => !newProgram.getSourceFileByPath(path as Path) && fileInfos.has(path))) { + + // Changed file: Update the version, set as changed file + oldInfo.version = version; + host.onSourceFileChanged(sourceFile.path); + } + + // Set the references + if (newReferences) { + referencedMap.set(sourceFile.path, newReferences); + } + else if (referencedMap) { + referencedMap.delete(sourceFile.path); + } + } + + // For removed files, remove the semantic diagnostics and file info + if (fileInfos.size > newProgram.getSourceFiles().length) { + fileInfos.forEach((_value, path) => { + if (!newProgram.getSourceFileByPath(path as Path)) { + fileInfos.delete(path); + host.onSourceFileRemoved(path as Path); + if (referencedMap) { + referencedMap.delete(path); + } + } + }); + } + } + + /** + * Gets the files affected by the path from the program + */ + function getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, cacheToUpdateSignature?: Map): ReadonlyArray { + // Since the operation could be cancelled, the signatures are always stored in the cache + // They will be commited once it is safe to use them + // eg when calling this api from tsserver, if there is no cancellation of the operation + // In the other cases the affected files signatures are commited only after the iteration through the result is complete + const signatureCache = cacheToUpdateSignature || createMap(); + const sourceFile = programOfThisState.getSourceFileByPath(path); + if (!sourceFile) { + return emptyArray; + } + + if (!updateShapeSignature(programOfThisState, sourceFile, signatureCache, cancellationToken)) { + return [sourceFile]; + } + + const result = getEmitDependentFilesAffectedBy(programOfThisState, sourceFile, signatureCache, cancellationToken); + if (!cacheToUpdateSignature) { + // Commit all the signatures in the signature cache + updateSignaturesFromCache(signatureCache); + } + return result; + } + + /** + * Updates the signatures from the cache + * This should be called whenever it is safe to commit the state of the builder + */ + function updateSignaturesFromCache(signatureCache: Map) { + signatureCache.forEach((signature, path) => { + fileInfos.get(path).signature = signature; + hasCalledUpdateShapeSignature.set(path, true); + }); + } + + /** + * For script files that contains only ambient external modules, although they are not actually external module files, + * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, + * there are no point to rebuild all script files if these special files have changed. However, if any statement + * in the file is not ambient external module, we treat it as a regular script file. + */ + function containsOnlyAmbientModules(sourceFile: SourceFile) { + for (const statement of sourceFile.statements) { + if (!isModuleWithStringLiteralName(statement)) { + return false; + } + } + return true; + } + + /** + * Returns if the shape of the signature has changed since last emit + * Note that it also updates the current signature as the latest signature for the file + */ + function updateShapeSignature(program: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) { + Debug.assert(!!sourceFile); + + // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate + if (hasCalledUpdateShapeSignature.has(sourceFile.path) || cacheToUpdateSignature.has(sourceFile.path)) { + return false; + } + + const info = fileInfos.get(sourceFile.path); + Debug.assert(!!info); + + const prevSignature = info.signature; + let latestSignature: string; + if (sourceFile.isDeclarationFile) { + latestSignature = sourceFile.version; + } + else { + const emitOutput = getFileEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken); + if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { + latestSignature = computeHash(emitOutput.outputFiles[0].text); + } + else { + latestSignature = prevSignature; + } + } + cacheToUpdateSignature.set(sourceFile.path, latestSignature); + + return !prevSignature || latestSignature !== prevSignature; + } + + /** + * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true + */ + function getReferencedFiles(program: Program, sourceFile: SourceFile): Map | undefined { + let referencedFiles: Map | undefined; + + // We need to use a set here since the code can contain the same import twice, + // but that will only be one dependency. + // To avoid invernal conversion, the key of the referencedFiles map must be of type Path + if (sourceFile.imports && sourceFile.imports.length > 0) { + const checker: TypeChecker = program.getTypeChecker(); + for (const importName of sourceFile.imports) { + const symbol = checker.getSymbolAtLocation(importName); + if (symbol && symbol.declarations && symbol.declarations[0]) { + const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]); + if (declarationSourceFile) { + addReferencedFile(declarationSourceFile.path); + } + } + } + } + + const sourceFileDirectory = getDirectoryPath(sourceFile.path); + // Handle triple slash references + if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { + for (const referencedFile of sourceFile.referencedFiles) { + const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); + addReferencedFile(referencedPath); + } + } + + // Handle type reference directives + if (sourceFile.resolvedTypeReferenceDirectiveNames) { + sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => { + if (!resolvedTypeReferenceDirective) { + return; + } + + const fileName = resolvedTypeReferenceDirective.resolvedFileName; + const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName); + addReferencedFile(typeFilePath); + }); + } + + return referencedFiles; + + function addReferencedFile(referencedPath: Path) { + if (!referencedFiles) { + referencedFiles = createMap(); + } + referencedFiles.set(referencedPath, true); + } + } + + /** + * Gets the files referenced by the the file path + */ + function getReferencedByPaths(referencedFilePath: Path) { + return mapDefinedIter(referencedMap.entries(), ([filePath, referencesInFile]) => + referencesInFile.has(referencedFilePath) ? filePath as Path : undefined + ); + } + + /** + * Gets all files of the program excluding the default library file + */ + function getAllFilesExcludingDefaultLibraryFile(program: Program, firstSourceFile: SourceFile): ReadonlyArray { + // Use cached result + if (allFilesExcludingDefaultLibraryFile) { + return allFilesExcludingDefaultLibraryFile; + } + + let result: SourceFile[]; + addSourceFile(firstSourceFile); + for (const sourceFile of program.getSourceFiles()) { + if (sourceFile !== firstSourceFile) { + addSourceFile(sourceFile); + } + } + allFilesExcludingDefaultLibraryFile = result || emptyArray; + return allFilesExcludingDefaultLibraryFile; + + function addSourceFile(sourceFile: SourceFile) { + if (!program.isSourceFileDefaultLibrary(sourceFile)) { + (result || (result = [])).push(sourceFile); + } + } + } + + /** + * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { + const compilerOptions = programOfThisState.getCompilerOptions(); + // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, + // so returning the file itself is good enough. + if (compilerOptions && (compilerOptions.out || compilerOptions.outFile)) { + return [sourceFileWithUpdatedShape]; + } + return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); + } + + /** + * When program emits modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) { + if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) { + return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); + } + + const compilerOptions = programOfThisState.getCompilerOptions(); + if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) { + return [sourceFileWithUpdatedShape]; + } + + // Now we need to if each file in the referencedBy list has a shape change as well. + // Because if so, its own referencedBy files need to be saved as well to make the + // emitting result consistent with files on disk. + const seenFileNamesMap = createMap(); + + // Start with the paths this file was referenced by + seenFileNamesMap.set(sourceFileWithUpdatedShape.path, sourceFileWithUpdatedShape); + const queue = getReferencedByPaths(sourceFileWithUpdatedShape.path); + while (queue.length > 0) { + const currentPath = queue.pop(); + if (!seenFileNamesMap.has(currentPath)) { + const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath); + seenFileNamesMap.set(currentPath, currentSourceFile); + if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken)) { + queue.push(...getReferencedByPaths(currentPath)); + } + } + } + + // Return array of values that needs emit + return flatMapIter(seenFileNamesMap.values(), value => value); + } + } +} diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 9960b501d78..7a8911a5cbb 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1,7 +1,6 @@ /// /// /// -/// namespace ts { const ignoreDiagnosticCommentRegEx = /(^\s*$)|(^\s*\/\/\/?\s*(@ts-ignore)?)/; diff --git a/src/server/project.ts b/src/server/project.ts index 1fa12e50120..a5542cb89e7 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -3,7 +3,7 @@ /// /// /// -/// +/// namespace ts.server { @@ -139,7 +139,7 @@ namespace ts.server { /*@internal*/ resolutionCache: ResolutionCache; - private builder: InternalBuilder | undefined; + private builder: BuilderState | undefined; /** * Set of files names that were updated since the last call to getChangesSinceVersion. */ @@ -461,9 +461,13 @@ namespace ts.server { } this.updateGraph(); if (!this.builder) { - this.builder = createInternalBuilder({ - useCaseSensitiveFileNames: () => this.useCaseSensitiveFileNames(), - createHash: data => this.projectService.host.createHash(data) + this.builder = createBuilderState({ + useCaseSensitiveFileNames: this.useCaseSensitiveFileNames(), + createHash: data => this.projectService.host.createHash(data), + onUpdateProgramInitialized: noop, + onSourceFileAdd: noop, + onSourceFileChanged: noop, + onSourceFileRemoved: noop }); } this.builder.updateProgram(this.program); From 2586bb303c976eb8f55a47e95a9b450690f8b119 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Dec 2017 12:39:26 -0800 Subject: [PATCH 035/172] From builder use the builderState containing references and file infos --- src/compiler/builder.ts | 480 ++---------------- src/compiler/builderState.ts | 214 +++++--- .../reference/api/tsserverlibrary.d.ts | 92 +--- tests/baselines/reference/api/typescript.d.ts | 62 +-- 4 files changed, 214 insertions(+), 634 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 9c7707bd1d8..ff372479fad 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -1,101 +1,26 @@ -/// +/// /*@internal*/ namespace ts { - export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, - cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput { - const outputFiles: OutputFile[] = []; - const emitResult = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); - return { outputFiles, emitSkipped: emitResult.emitSkipped }; - - function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) { - outputFiles.push({ name: fileName, writeByteOrderMark, text }); - } - } - - /** - * Internal Builder to get files affected by another file - */ - export interface InternalBuilder extends BaseBuilder { - /** - * Gets the files affected by the file path - * This api is only for internal use - */ - /*@internal*/ - getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken): ReadonlyArray; - } - - /** - * Create the internal builder to get files affected by sourceFile - */ - export function createInternalBuilder(host: BuilderHost): InternalBuilder { - return createBuilder(host, BuilderKind.BuilderKindInternal); - } - export enum BuilderKind { - BuilderKindInternal, BuilderKindSemanticDiagnostics, BuilderKindEmitAndSemanticDiagnostics } - /** - * Information about the source file: Its version and optional signature from last emit - */ - interface FileInfo { - version: string; - signature?: string; - } - - /** - * Referenced files with values for the keys as referenced file's path to be true - */ - type ReferencedSet = ReadonlyMap; - - function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined) { - if (map1 === undefined) { - return map2 === undefined; - } - if (map2 === undefined) { - return map1 === undefined; - } - // Has same size and every key is present in both maps - return map1.size === map2.size && !forEachEntry(map1, (_value, key) => !map2.has(key)); - } - - export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindInternal): InternalBuilder; export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindSemanticDiagnostics): SemanticDiagnosticsBuilder; export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindEmitAndSemanticDiagnostics): EmitAndSemanticDiagnosticsBuilder; export function createBuilder(host: BuilderHost, builderKind: BuilderKind) { /** - * Create the canonical file name for identity + * State corresponding to all the file references and shapes of the module etc */ - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); - /** - * Computing hash to for signature verification - */ - const computeHash = host.createHash || identity; - - /** - * Information of the file eg. its version, signature etc - */ - const fileInfos = createMap(); - - /** - * true if module emit is enabled - */ - let isModuleEmit: boolean; - - /** - * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled - * Otherwise undefined - */ - let referencedMap: Map | undefined; - - /** - * Get the files affected by the source file. - * This is dependent on whether its a module emit or not and hence function expression - */ - let getEmitDependentFilesAffectedBy: (programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) => ReadonlyArray; + const state = createBuilderState({ + useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(), + createHash: host.createHash, + onUpdateProgramInitialized, + onSourceFileAdd: addToChangedFilesSet, + onSourceFileChanged: path => { addToChangedFilesSet(path); deleteSemanticDiagnostics(path); }, + onSourceFileRemoved: deleteSemanticDiagnostics + }); /** * Cache of semantic diagnostics for files with their Path being the key @@ -107,18 +32,6 @@ namespace ts { */ const changedFilesSet = createMap(); - /** - * Map of files that have already called update signature. - * That means hence forth these files are assumed to have - * no change in their signature for this version of the program - */ - const hasCalledUpdateShapeSignature = createMap(); - - /** - * Cache of all files excluding default library file for the current program - */ - let allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; - /** * Set of affected files being iterated */ @@ -142,8 +55,6 @@ namespace ts { const seenAffectedFiles = createMap(); switch (builderKind) { - case BuilderKind.BuilderKindInternal: - return getInternalBuilder(); case BuilderKind.BuilderKindSemanticDiagnostics: return getSemanticDiagnosticsBuilder(); case BuilderKind.BuilderKindEmitAndSemanticDiagnostics: @@ -152,14 +63,6 @@ namespace ts { notImplemented(); } - function getInternalBuilder(): InternalBuilder { - return { - updateProgram, - getFilesAffectedBy, - getAllDependencies - }; - } - function getSemanticDiagnosticsBuilder(): SemanticDiagnosticsBuilder { return { updateProgram, @@ -179,17 +82,13 @@ namespace ts { } /** - * Update current state to reflect new program - * Updates changed files, references, file infos etc + * Initialize changedFiles, affected files set, cached diagnostics, signatures */ - function updateProgram(newProgram: Program) { - const newProgramHasModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None; - const oldReferencedMap = referencedMap; - if (isModuleEmit !== newProgramHasModuleEmit) { + function onUpdateProgramInitialized(isModuleEmitChanged: boolean) { + if (isModuleEmitChanged) { // Changes in the module emit, clear out everything and initialize as if first time // Clear file information and semantic diagnostics - fileInfos.clear(); semanticDiagnosticsPerFile.clear(); // Clear changed files and affected files information @@ -197,21 +96,12 @@ namespace ts { affectedFiles = undefined; currentChangedFilePath = undefined; currentAffectedFilesSignatures.clear(); - - // Update the reference map creation - referencedMap = newProgramHasModuleEmit ? createMap() : undefined; - - // Update the module emit - isModuleEmit = newProgramHasModuleEmit; - getEmitDependentFilesAffectedBy = isModuleEmit ? - getFilesAffectedByUpdatedShapeWhenModuleEmit : - getFilesAffectedByUpdatedShapeWhenNonModuleEmit; } else { if (currentChangedFilePath) { // Remove the diagnostics for all the affected files since we should resume the state such that // the whole iteration on currentChangedFile never happened - affectedFiles.map(sourceFile => semanticDiagnosticsPerFile.delete(sourceFile.path)); + affectedFiles.forEach(sourceFile => deleteSemanticDiagnostics(sourceFile.path)); affectedFiles = undefined; currentAffectedFilesSignatures.clear(); } @@ -219,100 +109,27 @@ namespace ts { // Verify the sanity of old state Debug.assert(!affectedFiles && !currentAffectedFilesSignatures.size, "Cannot reuse if only few affected files of currentChangedFile were iterated"); } - Debug.assert(!forEachEntry(changedFilesSet, (_value, path) => semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files"); - } - - // Clear datas that cant be retained beyond previous state - seenAffectedFiles.clear(); - hasCalledUpdateShapeSignature.clear(); - allFilesExcludingDefaultLibraryFile = undefined; - - // Create the reference map and update changed files - for (const sourceFile of newProgram.getSourceFiles()) { - const version = sourceFile.version; - const newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile); - const oldInfo = fileInfos.get(sourceFile.path); - let oldReferences: ReferencedSet; - - // Register changed file if its new file or we arent reusing old state - if (!oldInfo) { - // New file: Set the file info - fileInfos.set(sourceFile.path, { version }); - changedFilesSet.set(sourceFile.path, true); - } - // versions dont match - else if (oldInfo.version !== version || - // Referenced files changed - !hasSameKeys(newReferences, (oldReferences = oldReferencedMap && oldReferencedMap.get(sourceFile.path))) || - // Referenced file was deleted in the new program - newReferences && forEachEntry(newReferences, (_value, path) => !newProgram.getSourceFileByPath(path as Path) && fileInfos.has(path))) { - - // Changed file: Update the version, set as changed file - oldInfo.version = version; - changedFilesSet.set(sourceFile.path, true); - - // All changed files need to re-evaluate its semantic diagnostics - semanticDiagnosticsPerFile.delete(sourceFile.path); - } - - // Set the references - if (newReferences) { - referencedMap.set(sourceFile.path, newReferences); - } - else if (referencedMap) { - referencedMap.delete(sourceFile.path); - } - } - - // For removed files, remove the semantic diagnostics and file info - if (fileInfos.size > newProgram.getSourceFiles().length) { - fileInfos.forEach((_value, path) => { - if (!newProgram.getSourceFileByPath(path as Path)) { - fileInfos.delete(path); - semanticDiagnosticsPerFile.delete(path); - if (referencedMap) { - referencedMap.delete(path); - } - } - }); + Debug.assert(!forEachKey(changedFilesSet, path => semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files"); } } /** - * Gets the files affected by the path from the program + * Add file to the changed files set */ - function getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, cacheToUpdateSignature?: Map): ReadonlyArray { - // Since the operation could be cancelled, the signatures are always stored in the cache - // They will be commited once it is safe to use them - // eg when calling this api from tsserver, if there is no cancellation of the operation - // In the other cases the affected files signatures are commited only after the iteration through the result is complete - const signatureCache = cacheToUpdateSignature || createMap(); - const sourceFile = programOfThisState.getSourceFileByPath(path); - if (!sourceFile) { - return emptyArray; - } + function addToChangedFilesSet(path: Path) { + changedFilesSet.set(path, true); + } - if (!updateShapeSignature(programOfThisState, sourceFile, signatureCache, cancellationToken)) { - return [sourceFile]; - } - - const result = getEmitDependentFilesAffectedBy(programOfThisState, sourceFile, signatureCache, cancellationToken); - if (!cacheToUpdateSignature) { - // Commit all the signatures in the signature cache - updateSignaturesFromCache(signatureCache); - } - return result; + function deleteSemanticDiagnostics(path: Path) { + semanticDiagnosticsPerFile.delete(path); } /** - * Updates the signatures from the cache - * This should be called whenever it is safe to commit the state of the builder + * Update current state to reflect new program + * Updates changed files, references, file infos etc which happens through the state callbacks */ - function updateSignaturesFromCache(signatureCache: Map) { - signatureCache.forEach((signature, path) => { - fileInfos.get(path).signature = signature; - hasCalledUpdateShapeSignature.set(path, true); - }); + function updateProgram(newProgram: Program) { + state.updateProgram(newProgram); } /** @@ -339,7 +156,7 @@ namespace ts { changedFilesSet.delete(currentChangedFilePath); currentChangedFilePath = undefined; // Commit the changes in file signature - updateSignaturesFromCache(currentAffectedFilesSignatures); + state.updateSignaturesFromCache(currentAffectedFilesSignatures); currentAffectedFilesSignatures.clear(); affectedFiles = undefined; } @@ -361,7 +178,7 @@ namespace ts { // Get next batch of affected files currentAffectedFilesSignatures.clear(); - affectedFiles = getFilesAffectedBy(programOfThisState, nextKey.value as Path, cancellationToken, currentAffectedFilesSignatures); + affectedFiles = state.getFilesAffectedBy(programOfThisState, nextKey.value as Path, cancellationToken, currentAffectedFilesSignatures); currentChangedFilePath = nextKey.value as Path; semanticDiagnosticsPerFile.delete(currentChangedFilePath); affectedFilesIndex = 0; @@ -500,250 +317,13 @@ namespace ts { /** * Get all the dependencies of the sourceFile */ - function getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[] { - const compilerOptions = programOfThisState.getCompilerOptions(); - // With --out or --outFile all outputs go into single file, all files depend on each other - if (compilerOptions.outFile || compilerOptions.out) { - return programOfThisState.getSourceFiles().map(getFileName); - } - - // If this is non module emit, or its a global file, it depends on all the source files - if (!isModuleEmit || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) { - return programOfThisState.getSourceFiles().map(getFileName); - } - - // Get the references, traversing deep from the referenceMap - Debug.assert(!!referencedMap); - const seenMap = createMap(); - const queue = [sourceFile.path]; - while (queue.length) { - const path = queue.pop(); - if (!seenMap.has(path)) { - seenMap.set(path, true); - const references = referencedMap.get(path); - if (references) { - const iterator = references.keys(); - for (let { value, done } = iterator.next(); !done; { value, done } = iterator.next()) { - queue.push(value as Path); - } - } - } - } - - return flatMapIter(seenMap.keys(), path => { - const file = programOfThisState.getSourceFileByPath(path as Path); - if (file) { - return file.fileName; - } - return path; - }); - } - - function getFileName(sourceFile: SourceFile) { - return sourceFile.fileName; - } - - /** - * For script files that contains only ambient external modules, although they are not actually external module files, - * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, - * there are no point to rebuild all script files if these special files have changed. However, if any statement - * in the file is not ambient external module, we treat it as a regular script file. - */ - function containsOnlyAmbientModules(sourceFile: SourceFile) { - for (const statement of sourceFile.statements) { - if (!isModuleWithStringLiteralName(statement)) { - return false; - } - } - return true; - } - - /** - * Returns if the shape of the signature has changed since last emit - * Note that it also updates the current signature as the latest signature for the file - */ - function updateShapeSignature(program: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) { - Debug.assert(!!sourceFile); - - // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate - if (hasCalledUpdateShapeSignature.has(sourceFile.path) || cacheToUpdateSignature.has(sourceFile.path)) { - return false; - } - - const info = fileInfos.get(sourceFile.path); - Debug.assert(!!info); - - const prevSignature = info.signature; - let latestSignature: string; - if (sourceFile.isDeclarationFile) { - latestSignature = sourceFile.version; - } - else { - const emitOutput = getFileEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken); - if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { - latestSignature = computeHash(emitOutput.outputFiles[0].text); - } - else { - latestSignature = prevSignature; - } - } - cacheToUpdateSignature.set(sourceFile.path, latestSignature); - - return !prevSignature || latestSignature !== prevSignature; - } - - /** - * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true - */ - function getReferencedFiles(program: Program, sourceFile: SourceFile): Map | undefined { - let referencedFiles: Map | undefined; - - // We need to use a set here since the code can contain the same import twice, - // but that will only be one dependency. - // To avoid invernal conversion, the key of the referencedFiles map must be of type Path - if (sourceFile.imports && sourceFile.imports.length > 0) { - const checker: TypeChecker = program.getTypeChecker(); - for (const importName of sourceFile.imports) { - const symbol = checker.getSymbolAtLocation(importName); - if (symbol && symbol.declarations && symbol.declarations[0]) { - const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]); - if (declarationSourceFile) { - addReferencedFile(declarationSourceFile.path); - } - } - } - } - - const sourceFileDirectory = getDirectoryPath(sourceFile.path); - // Handle triple slash references - if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { - for (const referencedFile of sourceFile.referencedFiles) { - const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); - addReferencedFile(referencedPath); - } - } - - // Handle type reference directives - if (sourceFile.resolvedTypeReferenceDirectiveNames) { - sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => { - if (!resolvedTypeReferenceDirective) { - return; - } - - const fileName = resolvedTypeReferenceDirective.resolvedFileName; - const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName); - addReferencedFile(typeFilePath); - }); - } - - return referencedFiles; - - function addReferencedFile(referencedPath: Path) { - if (!referencedFiles) { - referencedFiles = createMap(); - } - referencedFiles.set(referencedPath, true); - } - } - - /** - * Gets the files referenced by the the file path - */ - function getReferencedByPaths(referencedFilePath: Path) { - return mapDefinedIter(referencedMap.entries(), ([filePath, referencesInFile]) => - referencesInFile.has(referencedFilePath) ? filePath as Path : undefined - ); - } - - /** - * Gets all files of the program excluding the default library file - */ - function getAllFilesExcludingDefaultLibraryFile(program: Program, firstSourceFile: SourceFile): ReadonlyArray { - // Use cached result - if (allFilesExcludingDefaultLibraryFile) { - return allFilesExcludingDefaultLibraryFile; - } - - let result: SourceFile[]; - addSourceFile(firstSourceFile); - for (const sourceFile of program.getSourceFiles()) { - if (sourceFile !== firstSourceFile) { - addSourceFile(sourceFile); - } - } - allFilesExcludingDefaultLibraryFile = result || emptyArray; - return allFilesExcludingDefaultLibraryFile; - - function addSourceFile(sourceFile: SourceFile) { - if (!program.isSourceFileDefaultLibrary(sourceFile)) { - (result || (result = [])).push(sourceFile); - } - } - } - - /** - * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed - */ - function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { - const compilerOptions = programOfThisState.getCompilerOptions(); - // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, - // so returning the file itself is good enough. - if (compilerOptions && (compilerOptions.out || compilerOptions.outFile)) { - return [sourceFileWithUpdatedShape]; - } - return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); - } - - /** - * When program emits modular code, gets the files affected by the sourceFile whose shape has changed - */ - function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) { - if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) { - return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); - } - - const compilerOptions = programOfThisState.getCompilerOptions(); - if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) { - return [sourceFileWithUpdatedShape]; - } - - // Now we need to if each file in the referencedBy list has a shape change as well. - // Because if so, its own referencedBy files need to be saved as well to make the - // emitting result consistent with files on disk. - const seenFileNamesMap = createMap(); - - // Start with the paths this file was referenced by - seenFileNamesMap.set(sourceFileWithUpdatedShape.path, sourceFileWithUpdatedShape); - const queue = getReferencedByPaths(sourceFileWithUpdatedShape.path); - while (queue.length > 0) { - const currentPath = queue.pop(); - if (!seenFileNamesMap.has(currentPath)) { - const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath); - seenFileNamesMap.set(currentPath, currentSourceFile); - if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken)) { - queue.push(...getReferencedByPaths(currentPath)); - } - } - } - - // Return array of values that needs emit - return flatMapIter(seenFileNamesMap.values(), value => value); + function getAllDependencies(programOfThisState: Program, sourceFile: SourceFile) { + return state.getAllDependencies(programOfThisState, sourceFile); } } } namespace ts { - export interface EmitOutput { - outputFiles: OutputFile[]; - emitSkipped: boolean; - } - - export interface OutputFile { - name: string; - writeByteOrderMark: boolean; - text: string; - } - export type AffectedFileResult = { result: T; affected: SourceFile | Program; } | undefined; export interface BuilderHost { @@ -769,7 +349,7 @@ namespace ts { /** * Get all the dependencies of the file */ - getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; + getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray; } /** diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index f6706195a75..eb3887eed15 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -46,7 +46,76 @@ namespace ts { return map1 === undefined; } // Has same size and every key is present in both maps - return map1.size === map2.size && !forEachEntry(map1, (_value, key) => !map2.has(key)); + return map1.size === map2.size && !forEachKey(map1, key => !map2.has(key)); + } + + /** + * For script files that contains only ambient external modules, although they are not actually external module files, + * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, + * there are no point to rebuild all script files if these special files have changed. However, if any statement + * in the file is not ambient external module, we treat it as a regular script file. + */ + function containsOnlyAmbientModules(sourceFile: SourceFile) { + for (const statement of sourceFile.statements) { + if (!isModuleWithStringLiteralName(statement)) { + return false; + } + } + return true; + } + + /** + * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true + */ + function getReferencedFiles(program: Program, sourceFile: SourceFile, getCanonicalFileName: GetCanonicalFileName): Map | undefined { + let referencedFiles: Map | undefined; + + // We need to use a set here since the code can contain the same import twice, + // but that will only be one dependency. + // To avoid invernal conversion, the key of the referencedFiles map must be of type Path + if (sourceFile.imports && sourceFile.imports.length > 0) { + const checker: TypeChecker = program.getTypeChecker(); + for (const importName of sourceFile.imports) { + const symbol = checker.getSymbolAtLocation(importName); + if (symbol && symbol.declarations && symbol.declarations[0]) { + const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]); + if (declarationSourceFile) { + addReferencedFile(declarationSourceFile.path); + } + } + } + } + + const sourceFileDirectory = getDirectoryPath(sourceFile.path); + // Handle triple slash references + if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { + for (const referencedFile of sourceFile.referencedFiles) { + const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); + addReferencedFile(referencedPath); + } + } + + // Handle type reference directives + if (sourceFile.resolvedTypeReferenceDirectiveNames) { + sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => { + if (!resolvedTypeReferenceDirective) { + return; + } + + const fileName = resolvedTypeReferenceDirective.resolvedFileName; + const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName); + addReferencedFile(typeFilePath); + }); + } + + return referencedFiles; + + function addReferencedFile(referencedPath: Path) { + if (!referencedFiles) { + referencedFiles = createMap(); + } + referencedFiles.set(referencedPath, true); + } } export interface BuilderStateHost { @@ -76,6 +145,15 @@ namespace ts { * Gets the files affected by the file path */ getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken, cacheToUpdateSignature?: Map): ReadonlyArray; + /** + * Updates the signatures from the cache + * This should be called whenever it is safe to commit the state of the builder + */ + updateSignaturesFromCache(signatureCache: Map): void; + /** + * Get all the dependencies of the sourceFile + */ + getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray; } export function createBuilderState(host: BuilderStateHost): BuilderState { @@ -121,11 +199,17 @@ namespace ts { * Cache of all files excluding default library file for the current program */ let allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; + /** + * Cache of all the file names + */ + let allFileNames: ReadonlyArray | undefined; return { updateProgram, getFilesAffectedBy, - }; + getAllDependencies, + updateSignaturesFromCache + }; /** * Update current state to reflect new program @@ -155,11 +239,12 @@ namespace ts { // Clear datas that cant be retained beyond previous state hasCalledUpdateShapeSignature.clear(); allFilesExcludingDefaultLibraryFile = undefined; + allFileNames = undefined; // Create the reference map and update changed files for (const sourceFile of newProgram.getSourceFiles()) { const version = sourceFile.version; - const newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile); + const newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile, getCanonicalFileName); const oldInfo = fileInfos.get(sourceFile.path); let oldReferences: ReferencedSet; @@ -174,7 +259,7 @@ namespace ts { // Referenced files changed !hasSameKeys(newReferences, (oldReferences = oldReferencedMap && oldReferencedMap.get(sourceFile.path))) || // Referenced file was deleted in the new program - newReferences && forEachEntry(newReferences, (_value, path) => !newProgram.getSourceFileByPath(path as Path) && fileInfos.has(path))) { + newReferences && forEachKey(newReferences, path => !newProgram.getSourceFileByPath(path as Path) && fileInfos.has(path))) { // Changed file: Update the version, set as changed file oldInfo.version = version; @@ -230,6 +315,58 @@ namespace ts { return result; } + /** + * Get all the dependencies of the sourceFile + */ + function getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray { + const compilerOptions = programOfThisState.getCompilerOptions(); + // With --out or --outFile all outputs go into single file, all files depend on each other + if (compilerOptions.outFile || compilerOptions.out) { + return getAllFileNames(programOfThisState); + } + + // If this is non module emit, or its a global file, it depends on all the source files + if (!isModuleEmit || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) { + return getAllFileNames(programOfThisState); + } + + // Get the references, traversing deep from the referenceMap + Debug.assert(!!referencedMap); + const seenMap = createMap(); + const queue = [sourceFile.path]; + while (queue.length) { + const path = queue.pop(); + if (!seenMap.has(path)) { + seenMap.set(path, true); + const references = referencedMap.get(path); + if (references) { + const iterator = references.keys(); + for (let { value, done } = iterator.next(); !done; { value, done } = iterator.next()) { + queue.push(value as Path); + } + } + } + } + + return flatMapIter(seenMap.keys(), path => { + const file = programOfThisState.getSourceFileByPath(path as Path); + if (file) { + return file.fileName; + } + return path; + }); + } + + /** + * Gets the names of all files from the program + */ + function getAllFileNames(programOfThisState: Program): ReadonlyArray { + if (!allFileNames) { + allFileNames = programOfThisState.getSourceFiles().map(file => file.fileName); + } + return allFileNames; + } + /** * Updates the signatures from the cache * This should be called whenever it is safe to commit the state of the builder @@ -241,21 +378,6 @@ namespace ts { }); } - /** - * For script files that contains only ambient external modules, although they are not actually external module files, - * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, - * there are no point to rebuild all script files if these special files have changed. However, if any statement - * in the file is not ambient external module, we treat it as a regular script file. - */ - function containsOnlyAmbientModules(sourceFile: SourceFile) { - for (const statement of sourceFile.statements) { - if (!isModuleWithStringLiteralName(statement)) { - return false; - } - } - return true; - } - /** * Returns if the shape of the signature has changed since last emit * Note that it also updates the current signature as the latest signature for the file @@ -290,60 +412,6 @@ namespace ts { return !prevSignature || latestSignature !== prevSignature; } - /** - * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true - */ - function getReferencedFiles(program: Program, sourceFile: SourceFile): Map | undefined { - let referencedFiles: Map | undefined; - - // We need to use a set here since the code can contain the same import twice, - // but that will only be one dependency. - // To avoid invernal conversion, the key of the referencedFiles map must be of type Path - if (sourceFile.imports && sourceFile.imports.length > 0) { - const checker: TypeChecker = program.getTypeChecker(); - for (const importName of sourceFile.imports) { - const symbol = checker.getSymbolAtLocation(importName); - if (symbol && symbol.declarations && symbol.declarations[0]) { - const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]); - if (declarationSourceFile) { - addReferencedFile(declarationSourceFile.path); - } - } - } - } - - const sourceFileDirectory = getDirectoryPath(sourceFile.path); - // Handle triple slash references - if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) { - for (const referencedFile of sourceFile.referencedFiles) { - const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName); - addReferencedFile(referencedPath); - } - } - - // Handle type reference directives - if (sourceFile.resolvedTypeReferenceDirectiveNames) { - sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => { - if (!resolvedTypeReferenceDirective) { - return; - } - - const fileName = resolvedTypeReferenceDirective.resolvedFileName; - const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName); - addReferencedFile(typeFilePath); - }); - } - - return referencedFiles; - - function addReferencedFile(referencedPath: Path) { - if (!referencedFiles) { - referencedFiles = createMap(); - } - referencedFiles.set(referencedPath, true); - } - } - /** * Gets the files referenced by the the file path */ diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index d7f3821a25d..32f4b78a8c5 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3757,87 +3757,6 @@ declare namespace ts { declare namespace ts { function createPrinter(printerOptions?: PrinterOptions, handlers?: PrintHandlers): Printer; } -declare namespace ts { - interface EmitOutput { - outputFiles: OutputFile[]; - emitSkipped: boolean; - } - interface OutputFile { - name: string; - writeByteOrderMark: boolean; - text: string; - } - type AffectedFileResult = { - result: T; - affected: SourceFile | Program; - } | undefined; - interface BuilderHost { - /** - * return true if file names are treated with case sensitivity - */ - useCaseSensitiveFileNames(): boolean; - /** - * If provided this would be used this hash instead of actual file shape text for detecting changes - */ - createHash?: (data: string) => string; - } - /** - * Builder to manage the program state changes - */ - interface BaseBuilder { - /** - * Updates the program in the builder to represent new state - */ - updateProgram(newProgram: Program): void; - /** - * Get all the dependencies of the file - */ - getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; - } - /** - * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files - */ - interface SemanticDiagnosticsBuilder extends BaseBuilder { - /** - * Gets the semantic diagnostics from the program for the next affected file and caches it - * Returns undefined if the iteration is complete - */ - getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; - /** - * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program - * The semantic diagnostics are cached and managed here - * Note that it is assumed that the when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - */ - getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; - } - /** - * The builder that can handle the changes in program and iterate through changed file to emit the files - * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files - */ - interface EmitAndSemanticDiagnosticsBuilder extends BaseBuilder { - /** - * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete - */ - emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult; - /** - * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program - * The semantic diagnostics are cached and managed here - * Note that it is assumed that the when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - */ - getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; - } - /** - * Create the builder to manage semantic diagnostics and cache them - */ - function createSemanticDiagnosticsBuilder(host: BuilderHost): SemanticDiagnosticsBuilder; - /** - * Create the builder that can handle the changes in program and iterate through changed files - * to emit the those files and manage semantic diagnostics cache as well - */ - function createEmitAndSemanticDiagnosticsBuilder(host: BuilderHost): EmitAndSemanticDiagnosticsBuilder; -} declare namespace ts { function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; function resolveTripleslashReference(moduleName: string, containingFile: string): string; @@ -7233,6 +7152,17 @@ declare namespace ts.server { onProjectClosed(project: Project): void; } } +declare namespace ts { + interface EmitOutput { + outputFiles: OutputFile[]; + emitSkipped: boolean; + } + interface OutputFile { + name: string; + writeByteOrderMark: boolean; + text: string; + } +} declare namespace ts.server { enum ProjectKind { Inferred = 0, diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 06c75e843c0..514c140f05e 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3704,6 +3704,35 @@ declare namespace ts { declare namespace ts { function createPrinter(printerOptions?: PrinterOptions, handlers?: PrintHandlers): Printer; } +declare namespace ts { + function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; + function resolveTripleslashReference(moduleName: string, containingFile: string): string; + function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost; + function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; + interface FormatDiagnosticsHost { + getCurrentDirectory(): string; + getCanonicalFileName(fileName: string): string; + getNewLine(): string; + } + function formatDiagnostics(diagnostics: ReadonlyArray, host: FormatDiagnosticsHost): string; + function formatDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost): string; + function formatDiagnosticsWithColorAndContext(diagnostics: ReadonlyArray, host: FormatDiagnosticsHost): string; + function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string; + /** + * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' + * that represent a compilation unit. + * + * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and + * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in. + * + * @param rootNames - A set of root files. + * @param options - The compiler options which should be used. + * @param host - The host interacts with the underlying file system. + * @param oldProgram - Reuses an old program structure. + * @returns A 'Program' object. + */ + function createProgram(rootNames: ReadonlyArray, options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program; +} declare namespace ts { interface EmitOutput { outputFiles: OutputFile[]; @@ -3714,6 +3743,8 @@ declare namespace ts { writeByteOrderMark: boolean; text: string; } +} +declare namespace ts { type AffectedFileResult = { result: T; affected: SourceFile | Program; @@ -3739,7 +3770,7 @@ declare namespace ts { /** * Get all the dependencies of the file */ - getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): string[]; + getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray; } /** * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files @@ -3785,35 +3816,6 @@ declare namespace ts { */ function createEmitAndSemanticDiagnosticsBuilder(host: BuilderHost): EmitAndSemanticDiagnosticsBuilder; } -declare namespace ts { - function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string; - function resolveTripleslashReference(moduleName: string, containingFile: string): string; - function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost; - function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; - interface FormatDiagnosticsHost { - getCurrentDirectory(): string; - getCanonicalFileName(fileName: string): string; - getNewLine(): string; - } - function formatDiagnostics(diagnostics: ReadonlyArray, host: FormatDiagnosticsHost): string; - function formatDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost): string; - function formatDiagnosticsWithColorAndContext(diagnostics: ReadonlyArray, host: FormatDiagnosticsHost): string; - function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string; - /** - * Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions' - * that represent a compilation unit. - * - * Creating a program proceeds from a set of root files, expanding the set of inputs by following imports and - * triple-slash-reference-path directives transitively. '@types' and triple-slash-reference-types are also pulled in. - * - * @param rootNames - A set of root files. - * @param options - The compiler options which should be used. - * @param host - The host interacts with the underlying file system. - * @param oldProgram - Reuses an old program structure. - * @returns A 'Program' object. - */ - function createProgram(rootNames: ReadonlyArray, options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program; -} declare namespace ts { type DiagnosticReporter = (diagnostic: Diagnostic) => void; /** From bb0fc0d2bcd88d99c148fbff650d6746b3bc7413 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Dec 2017 14:04:40 -0800 Subject: [PATCH 036/172] Convert builder state to mutable data, so that later we can create builder Program out of this --- src/compiler/builder.ts | 2 +- src/compiler/builderState.ts | 249 +++++++++++++++++++++++++++++++++-- src/server/project.ts | 20 +-- 3 files changed, 243 insertions(+), 28 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index ff372479fad..198bf0556f0 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -13,7 +13,7 @@ namespace ts { /** * State corresponding to all the file references and shapes of the module etc */ - const state = createBuilderState({ + const state = createBuilderStateOld({ useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(), createHash: host.createHash, onUpdateProgramInitialized, diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index eb3887eed15..0cd55ebd61f 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -136,7 +136,7 @@ namespace ts { onSourceFileRemoved(path: Path): void; } - export interface BuilderState { + export interface BuilderStateOld { /** * Updates the program in the builder to represent new state */ @@ -156,7 +156,65 @@ namespace ts { getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray; } - export function createBuilderState(host: BuilderStateHost): BuilderState { + export interface BuilderState { + /** + * Information of the file eg. its version, signature etc + */ + fileInfos: Map; + /** + * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled + * Otherwise undefined + * Thus non undefined value indicates, module emit + */ + readonly referencedMap: ReadonlyMap | undefined; + /** + * Map of files that have already called update signature. + * That means hence forth these files are assumed to have + * no change in their signature for this version of the program + */ + hasCalledUpdateShapeSignature: Map; + /** + * Cache of all files excluding default library file for the current program + */ + allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; + /** + * Cache of all the file names + */ + allFileNames: ReadonlyArray | undefined; + } + + export function createBuilderState(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: BuilderState): BuilderState { + const fileInfos = createMap(); + const referencedMap = newProgram.getCompilerOptions().module !== ModuleKind.None ? createMap() : undefined; + const hasCalledUpdateShapeSignature = createMap(); + const useOldState = oldState && !!oldState.referencedMap !== !!referencedMap; + + // Create the reference map, and set the file infos + for (const sourceFile of newProgram.getSourceFiles()) { + const version = sourceFile.version; + const oldInfo = useOldState && oldState.fileInfos.get(sourceFile.path); + if (referencedMap) { + const newReferences = getReferencedFiles(newProgram, sourceFile, getCanonicalFileName); + if (newReferences) { + referencedMap.set(sourceFile.path, newReferences); + } + } + fileInfos.set(sourceFile.path, { version, signature: oldInfo && oldInfo.signature }); + } + + oldState = undefined; + newProgram = undefined; + + return { + fileInfos, + referencedMap, + hasCalledUpdateShapeSignature, + allFilesExcludingDefaultLibraryFile: undefined, + allFileNames: undefined + }; + } + + export function createBuilderStateOld(host: BuilderStateHost): BuilderStateOld { /** * Create the canonical file name for identity */ @@ -171,11 +229,6 @@ namespace ts { */ const fileInfos = createMap(); - /** - * true if module emit is enabled - */ - let isModuleEmit: boolean; - /** * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled * Otherwise undefined @@ -218,7 +271,7 @@ namespace ts { function updateProgram(newProgram: Program) { const newProgramHasModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None; const oldReferencedMap = referencedMap; - const isModuleEmitChanged = isModuleEmit !== newProgramHasModuleEmit; + const isModuleEmitChanged = !!referencedMap !== newProgramHasModuleEmit; if (isModuleEmitChanged) { // Changes in the module emit, clear out everything and initialize as if first time @@ -229,8 +282,7 @@ namespace ts { referencedMap = newProgramHasModuleEmit ? createMap() : undefined; // Update the module emit - isModuleEmit = newProgramHasModuleEmit; - getEmitDependentFilesAffectedBy = isModuleEmit ? + getEmitDependentFilesAffectedBy = newProgramHasModuleEmit ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit; } @@ -326,12 +378,11 @@ namespace ts { } // If this is non module emit, or its a global file, it depends on all the source files - if (!isModuleEmit || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) { + if (!referencedMap || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) { return getAllFileNames(programOfThisState); } // Get the references, traversing deep from the referenceMap - Debug.assert(!!referencedMap); const seenMap = createMap(); const queue = [sourceFile.path]; while (queue.length) { @@ -497,3 +548,177 @@ namespace ts { } } } + +/*@internal*/ +namespace ts.BuilderState { + type ComputeHash = (data: string) => string; + + /** + * Gets the files affected by the path from the program + */ + export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash?: ComputeHash, cacheToUpdateSignature?: Map): ReadonlyArray { + // Since the operation could be cancelled, the signatures are always stored in the cache + // They will be commited once it is safe to use them + // eg when calling this api from tsserver, if there is no cancellation of the operation + // In the other cases the affected files signatures are commited only after the iteration through the result is complete + const signatureCache = cacheToUpdateSignature || createMap(); + const sourceFile = programOfThisState.getSourceFileByPath(path); + if (!sourceFile) { + return emptyArray; + } + + if (!updateShapeSignature(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash)) { + return [sourceFile]; + } + + const result = (state.referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit)(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash); + if (!cacheToUpdateSignature) { + // Commit all the signatures in the signature cache + updateSignaturesFromCache(state, signatureCache); + } + return result; + } + + /** + * Updates the signatures from the cache into state's fileinfo signatures + * This should be called whenever it is safe to commit the state of the builder + */ + export function updateSignaturesFromCache(state: BuilderState, signatureCache: Map) { + signatureCache.forEach((signature, path) => { + state.fileInfos.get(path).signature = signature; + state.hasCalledUpdateShapeSignature.set(path, true); + }); + } + + /** + * Returns if the shape of the signature has changed since last emit + */ + function updateShapeSignature(state: Readonly, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash | undefined) { + Debug.assert(!!sourceFile); + + // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate + if (state.hasCalledUpdateShapeSignature.has(sourceFile.path) || cacheToUpdateSignature.has(sourceFile.path)) { + return false; + } + + const info = state.fileInfos.get(sourceFile.path); + Debug.assert(!!info); + + const prevSignature = info.signature; + let latestSignature: string; + if (sourceFile.isDeclarationFile) { + latestSignature = sourceFile.version; + } + else { + const emitOutput = getFileEmitOutput(programOfThisState, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken); + if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { + latestSignature = (computeHash || identity)(emitOutput.outputFiles[0].text); + } + else { + latestSignature = prevSignature; + } + } + cacheToUpdateSignature.set(sourceFile.path, latestSignature); + + return !prevSignature || latestSignature !== prevSignature; + } + + /** + * Gets the files referenced by the the file path + */ + function getReferencedByPaths(state: Readonly, referencedFilePath: Path) { + return mapDefinedIter(state.referencedMap.entries(), ([filePath, referencesInFile]) => + referencesInFile.has(referencedFilePath) ? filePath as Path : undefined + ); + } + + /** + * For script files that contains only ambient external modules, although they are not actually external module files, + * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, + * there are no point to rebuild all script files if these special files have changed. However, if any statement + * in the file is not ambient external module, we treat it as a regular script file. + */ + function containsOnlyAmbientModules(sourceFile: SourceFile) { + for (const statement of sourceFile.statements) { + if (!isModuleWithStringLiteralName(statement)) { + return false; + } + } + return true; + } + + /** + * Gets all files of the program excluding the default library file + */ + function getAllFilesExcludingDefaultLibraryFile(state: BuilderState, programOfThisState: Program, firstSourceFile: SourceFile): ReadonlyArray { + // Use cached result + if (state.allFilesExcludingDefaultLibraryFile) { + return state.allFilesExcludingDefaultLibraryFile; + } + + let result: SourceFile[]; + addSourceFile(firstSourceFile); + for (const sourceFile of programOfThisState.getSourceFiles()) { + if (sourceFile !== firstSourceFile) { + addSourceFile(sourceFile); + } + } + state.allFilesExcludingDefaultLibraryFile = result || emptyArray; + return state.allFilesExcludingDefaultLibraryFile; + + function addSourceFile(sourceFile: SourceFile) { + if (!programOfThisState.isSourceFileDefaultLibrary(sourceFile)) { + (result || (result = [])).push(sourceFile); + } + } + } + + /** + * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { + const compilerOptions = programOfThisState.getCompilerOptions(); + // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, + // so returning the file itself is good enough. + if (compilerOptions && (compilerOptions.out || compilerOptions.outFile)) { + return [sourceFileWithUpdatedShape]; + } + return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); + } + + /** + * When program emits modular code, gets the files affected by the sourceFile whose shape has changed + */ + function getFilesAffectedByUpdatedShapeWhenModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash | undefined) { + if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) { + return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape); + } + + const compilerOptions = programOfThisState.getCompilerOptions(); + if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) { + return [sourceFileWithUpdatedShape]; + } + + // Now we need to if each file in the referencedBy list has a shape change as well. + // Because if so, its own referencedBy files need to be saved as well to make the + // emitting result consistent with files on disk. + const seenFileNamesMap = createMap(); + + // Start with the paths this file was referenced by + seenFileNamesMap.set(sourceFileWithUpdatedShape.path, sourceFileWithUpdatedShape); + const queue = getReferencedByPaths(state, sourceFileWithUpdatedShape.path); + while (queue.length > 0) { + const currentPath = queue.pop(); + if (!seenFileNamesMap.has(currentPath)) { + const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath); + seenFileNamesMap.set(currentPath, currentSourceFile); + if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken, computeHash)) { + queue.push(...getReferencedByPaths(state, currentPath)); + } + } + } + + // Return array of values that needs emit + return flatMapIter(seenFileNamesMap.values(), value => value); + } +} diff --git a/src/server/project.ts b/src/server/project.ts index a5542cb89e7..95c41eaaf44 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -139,7 +139,7 @@ namespace ts.server { /*@internal*/ resolutionCache: ResolutionCache; - private builder: BuilderState | undefined; + private builderState: BuilderState | undefined; /** * Set of files names that were updated since the last call to getChangesSinceVersion. */ @@ -460,18 +460,8 @@ namespace ts.server { return []; } this.updateGraph(); - if (!this.builder) { - this.builder = createBuilderState({ - useCaseSensitiveFileNames: this.useCaseSensitiveFileNames(), - createHash: data => this.projectService.host.createHash(data), - onUpdateProgramInitialized: noop, - onSourceFileAdd: noop, - onSourceFileChanged: noop, - onSourceFileRemoved: noop - }); - } - this.builder.updateProgram(this.program); - return mapDefined(this.builder.getFilesAffectedBy(this.program, scriptInfo.path, this.cancellationToken), + this.builderState = createBuilderState(this.program, this.projectService.toCanonicalFileName, this.builderState); + return mapDefined(BuilderState.getFilesAffectedBy(this.builderState, this.program, scriptInfo.path, this.cancellationToken, data => this.projectService.host.createHash(data)), sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined); } @@ -507,7 +497,7 @@ namespace ts.server { } this.languageService.cleanupSemanticCache(); this.languageServiceEnabled = false; - this.builder = undefined; + this.builderState = undefined; this.resolutionCache.closeTypeRootsWatch(); this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ false); } @@ -548,7 +538,7 @@ namespace ts.server { this.rootFilesMap = undefined; this.externalFiles = undefined; this.program = undefined; - this.builder = undefined; + this.builderState = undefined; this.resolutionCache.clear(); this.resolutionCache = undefined; this.cachedUnresolvedImportsPerFile = undefined; From 965f40f2132a9aa09c6647eecc6f12b0800e7aaa Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Dec 2017 16:46:36 -0800 Subject: [PATCH 037/172] Use builder state in the semantic/emit builder as well --- src/compiler/builder.ts | 204 ++++--- src/compiler/builderState.ts | 540 ++++-------------- src/server/project.ts | 2 +- .../reference/api/tsserverlibrary.d.ts | 2 +- 4 files changed, 219 insertions(+), 529 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 198bf0556f0..110bdd5130a 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -7,52 +7,118 @@ namespace ts { BuilderKindEmitAndSemanticDiagnostics } - export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindSemanticDiagnostics): SemanticDiagnosticsBuilder; - export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindEmitAndSemanticDiagnostics): EmitAndSemanticDiagnosticsBuilder; - export function createBuilder(host: BuilderHost, builderKind: BuilderKind) { - /** - * State corresponding to all the file references and shapes of the module etc - */ - const state = createBuilderStateOld({ - useCaseSensitiveFileNames: host.useCaseSensitiveFileNames(), - createHash: host.createHash, - onUpdateProgramInitialized, - onSourceFileAdd: addToChangedFilesSet, - onSourceFileChanged: path => { addToChangedFilesSet(path); deleteSemanticDiagnostics(path); }, - onSourceFileRemoved: deleteSemanticDiagnostics - }); - + interface BuilderStateWithChangedFiles extends BuilderState { /** * Cache of semantic diagnostics for files with their Path being the key */ - const semanticDiagnosticsPerFile = createMap>(); - + semanticDiagnosticsPerFile: Map> | undefined; /** * The map has key by source file's path that has been changed */ - const changedFilesSet = createMap(); - + changedFilesSet: Map; /** * Set of affected files being iterated */ - let affectedFiles: ReadonlyArray | undefined; + affectedFiles: ReadonlyArray | undefined; /** * Current index to retrieve affected file from */ - let affectedFilesIndex = 0; + affectedFilesIndex: number | undefined; /** * Current changed file for iterating over affected files */ - let currentChangedFilePath: Path | undefined; + currentChangedFilePath: Path | undefined; /** * Map of file signatures, with key being file path, calculated while getting current changed file's affected files * These will be commited whenever the iteration through affected files of current changed file is complete */ - const currentAffectedFilesSignatures = createMap(); + currentAffectedFilesSignatures: Map | undefined; /** * Already seen affected files */ - const seenAffectedFiles = createMap(); + seenAffectedFiles: Map | undefined; + } + + function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined) { + if (map1 === undefined) { + return map2 === undefined; + } + if (map2 === undefined) { + return map1 === undefined; + } + // Has same size and every key is present in both maps + return map1.size === map2.size && !forEachKey(map1, key => !map2.has(key)); + } + + /** + * Create the state so that we can iterate on changedFiles/affected files + */ + function createBuilderStateWithChangedFiles(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly): BuilderStateWithChangedFiles { + const state = BuilderState.create(newProgram, getCanonicalFileName, oldState) as BuilderStateWithChangedFiles; + const compilerOptions = newProgram.getCompilerOptions(); + if (!compilerOptions.outFile && !compilerOptions.out) { + state.semanticDiagnosticsPerFile = createMap>(); + } + state.changedFilesSet = createMap(); + const useOldState = BuilderState.canReuseOldState(state.referencedMap, oldState); + const canCopySemanticDiagnostics = useOldState && oldState.semanticDiagnosticsPerFile && !!state.semanticDiagnosticsPerFile; + if (useOldState) { + // Verify the sanity of old state + if (!oldState.currentChangedFilePath) { + Debug.assert(!oldState.affectedFiles && (!oldState.currentAffectedFilesSignatures || !oldState.currentAffectedFilesSignatures.size), "Cannot reuse if only few affected files of currentChangedFile were iterated"); + } + if (canCopySemanticDiagnostics) { + Debug.assert(!forEachKey(oldState.changedFilesSet, path => oldState.semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files"); + } + + // Copy old state's changed files set + copyEntries(oldState.changedFilesSet, state.changedFilesSet); + } + + // Update changed files and copy semantic diagnostics if we can + const referencedMap = state.referencedMap; + const oldReferencedMap = useOldState && oldState.referencedMap; + state.fileInfos.forEach((info, sourceFilePath) => { + let oldInfo: Readonly; + let newReferences: BuilderState.ReferencedSet; + + // if not using old state, every file is changed + if (!useOldState || + // File wasnt present in old state + !(oldInfo = oldState.fileInfos.get(sourceFilePath)) || + // versions dont match + oldInfo.version !== info.version || + // Referenced files changed + !hasSameKeys(newReferences = referencedMap && referencedMap.get(sourceFilePath), oldReferencedMap && oldReferencedMap.get(sourceFilePath)) || + // Referenced file was deleted in the new program + newReferences && forEachKey(newReferences, path => !state.fileInfos.has(path) && oldState.fileInfos.has(path))) { + // Register file as changed file and do not copy semantic diagnostics, since all changed files need to be re-evaluated + state.changedFilesSet.set(sourceFilePath, true); + } + else if (canCopySemanticDiagnostics) { + // Unchanged file copy diagnostics + const diagnostics = oldState.semanticDiagnosticsPerFile.get(sourceFilePath); + if (diagnostics) { + state.semanticDiagnosticsPerFile.set(sourceFilePath, diagnostics); + } + } + }); + + return state; + } + + export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindSemanticDiagnostics): SemanticDiagnosticsBuilder; + export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindEmitAndSemanticDiagnostics): EmitAndSemanticDiagnosticsBuilder; + export function createBuilder(host: BuilderHost, builderKind: BuilderKind) { + /** + * Create the canonical file name for identity + */ + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); + /** + * Computing hash to for signature verification + */ + const computeHash = host.createHash || identity; + let state: BuilderStateWithChangedFiles; switch (builderKind) { case BuilderKind.BuilderKindSemanticDiagnostics: @@ -81,55 +147,12 @@ namespace ts { }; } - /** - * Initialize changedFiles, affected files set, cached diagnostics, signatures - */ - function onUpdateProgramInitialized(isModuleEmitChanged: boolean) { - if (isModuleEmitChanged) { - // Changes in the module emit, clear out everything and initialize as if first time - - // Clear file information and semantic diagnostics - semanticDiagnosticsPerFile.clear(); - - // Clear changed files and affected files information - changedFilesSet.clear(); - affectedFiles = undefined; - currentChangedFilePath = undefined; - currentAffectedFilesSignatures.clear(); - } - else { - if (currentChangedFilePath) { - // Remove the diagnostics for all the affected files since we should resume the state such that - // the whole iteration on currentChangedFile never happened - affectedFiles.forEach(sourceFile => deleteSemanticDiagnostics(sourceFile.path)); - affectedFiles = undefined; - currentAffectedFilesSignatures.clear(); - } - else { - // Verify the sanity of old state - Debug.assert(!affectedFiles && !currentAffectedFilesSignatures.size, "Cannot reuse if only few affected files of currentChangedFile were iterated"); - } - Debug.assert(!forEachKey(changedFilesSet, path => semanticDiagnosticsPerFile.has(path)), "Semantic diagnostics shouldnt be available for changed files"); - } - } - - /** - * Add file to the changed files set - */ - function addToChangedFilesSet(path: Path) { - changedFilesSet.set(path, true); - } - - function deleteSemanticDiagnostics(path: Path) { - semanticDiagnosticsPerFile.delete(path); - } - /** * Update current state to reflect new program * Updates changed files, references, file infos etc which happens through the state callbacks */ function updateProgram(newProgram: Program) { - state.updateProgram(newProgram); + state = createBuilderStateWithChangedFiles(newProgram, getCanonicalFileName, state); } /** @@ -140,11 +163,15 @@ namespace ts { */ function getNextAffectedFile(programOfThisState: Program, cancellationToken: CancellationToken | undefined): SourceFile | Program | undefined { while (true) { + const { affectedFiles } = state; if (affectedFiles) { + const { seenAffectedFiles, semanticDiagnosticsPerFile } = state; + let { affectedFilesIndex } = state; while (affectedFilesIndex < affectedFiles.length) { const affectedFile = affectedFiles[affectedFilesIndex]; if (!seenAffectedFiles.has(affectedFile.path)) { // Set the next affected file as seen and remove the cached semantic diagnostics + state.affectedFilesIndex = affectedFilesIndex; semanticDiagnosticsPerFile.delete(affectedFile.path); return affectedFile; } @@ -153,16 +180,16 @@ namespace ts { } // Remove the changed file from the change set - changedFilesSet.delete(currentChangedFilePath); - currentChangedFilePath = undefined; + state.changedFilesSet.delete(state.currentChangedFilePath); + state.currentChangedFilePath = undefined; // Commit the changes in file signature - state.updateSignaturesFromCache(currentAffectedFilesSignatures); - currentAffectedFilesSignatures.clear(); - affectedFiles = undefined; + BuilderState.updateSignaturesFromCache(state, state.currentAffectedFilesSignatures); + state.currentAffectedFilesSignatures.clear(); + state.affectedFiles = undefined; } // Get next changed file - const nextKey = changedFilesSet.keys().next(); + const nextKey = state.changedFilesSet.keys().next(); if (nextKey.done) { // Done return undefined; @@ -172,16 +199,17 @@ namespace ts { // With --out or --outFile all outputs go into single file // so operations are performed directly on program, return program if (compilerOptions.outFile || compilerOptions.out) { - Debug.assert(semanticDiagnosticsPerFile.size === 0); + Debug.assert(!state.semanticDiagnosticsPerFile); return programOfThisState; } // Get next batch of affected files - currentAffectedFilesSignatures.clear(); - affectedFiles = state.getFilesAffectedBy(programOfThisState, nextKey.value as Path, cancellationToken, currentAffectedFilesSignatures); - currentChangedFilePath = nextKey.value as Path; - semanticDiagnosticsPerFile.delete(currentChangedFilePath); - affectedFilesIndex = 0; + state.currentAffectedFilesSignatures = state.currentAffectedFilesSignatures || createMap(); + state.affectedFiles = BuilderState.getFilesAffectedBy(state, programOfThisState, nextKey.value as Path, cancellationToken, computeHash, state.currentAffectedFilesSignatures); + state.currentChangedFilePath = nextKey.value as Path; + state.semanticDiagnosticsPerFile.delete(nextKey.value as Path); + state.affectedFilesIndex = 0; + state.seenAffectedFiles = state.seenAffectedFiles || createMap(); } } @@ -191,11 +219,11 @@ namespace ts { */ function doneWithAffectedFile(programOfThisState: Program, affected: SourceFile | Program) { if (affected === programOfThisState) { - changedFilesSet.clear(); + state.changedFilesSet.clear(); } else { - seenAffectedFiles.set((affected).path, true); - affectedFilesIndex++; + state.seenAffectedFiles.set((affected as SourceFile).path, true); + state.affectedFilesIndex++; } } @@ -277,10 +305,10 @@ namespace ts { * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files */ function getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { - Debug.assert(!affectedFiles || affectedFiles[affectedFilesIndex - 1] !== sourceFile || !semanticDiagnosticsPerFile.has(sourceFile.path)); + Debug.assert(!state.affectedFiles || state.affectedFiles[state.affectedFilesIndex - 1] !== sourceFile || !state.semanticDiagnosticsPerFile.has(sourceFile.path)); const compilerOptions = programOfThisState.getCompilerOptions(); if (compilerOptions.outFile || compilerOptions.out) { - Debug.assert(semanticDiagnosticsPerFile.size === 0); + Debug.assert(!state.semanticDiagnosticsPerFile); // We dont need to cache the diagnostics just return them from program return programOfThisState.getSemanticDiagnostics(sourceFile, cancellationToken); } @@ -302,7 +330,7 @@ namespace ts { */ function getSemanticDiagnosticsOfFile(program: Program, sourceFile: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { const path = sourceFile.path; - const cachedDiagnostics = semanticDiagnosticsPerFile.get(path); + const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path); // Report the semantic diagnostics from the cache if we already have those diagnostics present if (cachedDiagnostics) { return cachedDiagnostics; @@ -310,7 +338,7 @@ namespace ts { // Diagnostics werent cached, get them from program, and cache the result const diagnostics = program.getSemanticDiagnostics(sourceFile, cancellationToken); - semanticDiagnosticsPerFile.set(path, diagnostics); + state.semanticDiagnosticsPerFile.set(path, diagnostics); return diagnostics; } @@ -318,7 +346,7 @@ namespace ts { * Get all the dependencies of the sourceFile */ function getAllDependencies(programOfThisState: Program, sourceFile: SourceFile) { - return state.getAllDependencies(programOfThisState, sourceFile); + return BuilderState.getAllDependencies(state, programOfThisState, sourceFile); } } } diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index 0cd55ebd61f..79ba3767b93 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -25,44 +25,51 @@ namespace ts { } } + export interface BuilderState { + /** + * Information of the file eg. its version, signature etc + */ + fileInfos: Map; + /** + * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled + * Otherwise undefined + * Thus non undefined value indicates, module emit + */ + readonly referencedMap: ReadonlyMap | undefined; + /** + * Map of files that have already called update signature. + * That means hence forth these files are assumed to have + * no change in their signature for this version of the program + */ + hasCalledUpdateShapeSignature: Map; + /** + * Cache of all files excluding default library file for the current program + */ + allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; + /** + * Cache of all the file names + */ + allFileNames: ReadonlyArray | undefined; + } +} + +/*@internal*/ +namespace ts.BuilderState { /** * Information about the source file: Its version and optional signature from last emit */ - interface FileInfo { - version: string; - signature?: string; + export interface FileInfo { + readonly version: string; + signature: string | undefined; } - /** * Referenced files with values for the keys as referenced file's path to be true */ - type ReferencedSet = ReadonlyMap; - - function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined) { - if (map1 === undefined) { - return map2 === undefined; - } - if (map2 === undefined) { - return map1 === undefined; - } - // Has same size and every key is present in both maps - return map1.size === map2.size && !forEachKey(map1, key => !map2.has(key)); - } - + export type ReferencedSet = ReadonlyMap; /** - * For script files that contains only ambient external modules, although they are not actually external module files, - * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore, - * there are no point to rebuild all script files if these special files have changed. However, if any statement - * in the file is not ambient external module, we treat it as a regular script file. + * Compute the hash to store the shape of the file */ - function containsOnlyAmbientModules(sourceFile: SourceFile) { - for (const statement of sourceFile.statements) { - if (!isModuleWithStringLiteralName(statement)) { - return false; - } - } - return true; - } + export type ComputeHash = (data: string) => string; /** * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true @@ -118,76 +125,21 @@ namespace ts { } } - export interface BuilderStateHost { - /** - * true if file names are treated with case sensitivity - */ - useCaseSensitiveFileNames: boolean; - /** - * if provided this would be used this hash instead of actual file shape text for detecting changes - */ - createHash?: (data: string) => string; - /** - * Called when programState is initialized, indicating if isModuleEmit is changed - */ - onUpdateProgramInitialized(isModuleEmitChanged: boolean): void; - onSourceFileAdd(path: Path): void; - onSourceFileChanged(path: Path): void; - onSourceFileRemoved(path: Path): void; + /** + * Returns true if oldState is reusable, that is the emitKind = module/non module has not changed + */ + export function canReuseOldState(newReferencedMap: ReadonlyMap, oldState: Readonly | undefined) { + return oldState && !oldState.referencedMap === !newReferencedMap; } - export interface BuilderStateOld { - /** - * Updates the program in the builder to represent new state - */ - updateProgram(newProgram: Program): void; - /** - * Gets the files affected by the file path - */ - getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken, cacheToUpdateSignature?: Map): ReadonlyArray; - /** - * Updates the signatures from the cache - * This should be called whenever it is safe to commit the state of the builder - */ - updateSignaturesFromCache(signatureCache: Map): void; - /** - * Get all the dependencies of the sourceFile - */ - getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray; - } - - export interface BuilderState { - /** - * Information of the file eg. its version, signature etc - */ - fileInfos: Map; - /** - * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled - * Otherwise undefined - * Thus non undefined value indicates, module emit - */ - readonly referencedMap: ReadonlyMap | undefined; - /** - * Map of files that have already called update signature. - * That means hence forth these files are assumed to have - * no change in their signature for this version of the program - */ - hasCalledUpdateShapeSignature: Map; - /** - * Cache of all files excluding default library file for the current program - */ - allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; - /** - * Cache of all the file names - */ - allFileNames: ReadonlyArray | undefined; - } - - export function createBuilderState(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: BuilderState): BuilderState { + /** + * Creates the state of file references and signature for the new program from oldState if it is safe + */ + export function create(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly): BuilderState { const fileInfos = createMap(); const referencedMap = newProgram.getCompilerOptions().module !== ModuleKind.None ? createMap() : undefined; const hasCalledUpdateShapeSignature = createMap(); - const useOldState = oldState && !!oldState.referencedMap !== !!referencedMap; + const useOldState = canReuseOldState(referencedMap, oldState); // Create the reference map, and set the file infos for (const sourceFile of newProgram.getSourceFiles()) { @@ -202,9 +154,6 @@ namespace ts { fileInfos.set(sourceFile.path, { version, signature: oldInfo && oldInfo.signature }); } - oldState = undefined; - newProgram = undefined; - return { fileInfos, referencedMap, @@ -214,349 +163,10 @@ namespace ts { }; } - export function createBuilderStateOld(host: BuilderStateHost): BuilderStateOld { - /** - * Create the canonical file name for identity - */ - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); - /** - * Computing hash to for signature verification - */ - const computeHash = host.createHash || identity; - - /** - * Information of the file eg. its version, signature etc - */ - const fileInfos = createMap(); - - /** - * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled - * Otherwise undefined - */ - let referencedMap: Map | undefined; - - /** - * Get the files affected by the source file. - * This is dependent on whether its a module emit or not and hence function expression - */ - let getEmitDependentFilesAffectedBy: (programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) => ReadonlyArray; - - /** - * Map of files that have already called update signature. - * That means hence forth these files are assumed to have - * no change in their signature for this version of the program - */ - const hasCalledUpdateShapeSignature = createMap(); - - /** - * Cache of all files excluding default library file for the current program - */ - let allFilesExcludingDefaultLibraryFile: ReadonlyArray | undefined; - /** - * Cache of all the file names - */ - let allFileNames: ReadonlyArray | undefined; - - return { - updateProgram, - getFilesAffectedBy, - getAllDependencies, - updateSignaturesFromCache - }; - - /** - * Update current state to reflect new program - * Updates changed files, references, file infos etc - */ - function updateProgram(newProgram: Program) { - const newProgramHasModuleEmit = newProgram.getCompilerOptions().module !== ModuleKind.None; - const oldReferencedMap = referencedMap; - const isModuleEmitChanged = !!referencedMap !== newProgramHasModuleEmit; - if (isModuleEmitChanged) { - // Changes in the module emit, clear out everything and initialize as if first time - - // Clear file information - fileInfos.clear(); - - // Update the reference map creation - referencedMap = newProgramHasModuleEmit ? createMap() : undefined; - - // Update the module emit - getEmitDependentFilesAffectedBy = newProgramHasModuleEmit ? - getFilesAffectedByUpdatedShapeWhenModuleEmit : - getFilesAffectedByUpdatedShapeWhenNonModuleEmit; - } - host.onUpdateProgramInitialized(isModuleEmitChanged); - - // Clear datas that cant be retained beyond previous state - hasCalledUpdateShapeSignature.clear(); - allFilesExcludingDefaultLibraryFile = undefined; - allFileNames = undefined; - - // Create the reference map and update changed files - for (const sourceFile of newProgram.getSourceFiles()) { - const version = sourceFile.version; - const newReferences = referencedMap && getReferencedFiles(newProgram, sourceFile, getCanonicalFileName); - const oldInfo = fileInfos.get(sourceFile.path); - let oldReferences: ReferencedSet; - - // Register changed file if its new file or we arent reusing old state - if (!oldInfo) { - // New file: Set the file info - fileInfos.set(sourceFile.path, { version }); - host.onSourceFileAdd(sourceFile.path); - } - // versions dont match - else if (oldInfo.version !== version || - // Referenced files changed - !hasSameKeys(newReferences, (oldReferences = oldReferencedMap && oldReferencedMap.get(sourceFile.path))) || - // Referenced file was deleted in the new program - newReferences && forEachKey(newReferences, path => !newProgram.getSourceFileByPath(path as Path) && fileInfos.has(path))) { - - // Changed file: Update the version, set as changed file - oldInfo.version = version; - host.onSourceFileChanged(sourceFile.path); - } - - // Set the references - if (newReferences) { - referencedMap.set(sourceFile.path, newReferences); - } - else if (referencedMap) { - referencedMap.delete(sourceFile.path); - } - } - - // For removed files, remove the semantic diagnostics and file info - if (fileInfos.size > newProgram.getSourceFiles().length) { - fileInfos.forEach((_value, path) => { - if (!newProgram.getSourceFileByPath(path as Path)) { - fileInfos.delete(path); - host.onSourceFileRemoved(path as Path); - if (referencedMap) { - referencedMap.delete(path); - } - } - }); - } - } - - /** - * Gets the files affected by the path from the program - */ - function getFilesAffectedBy(programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, cacheToUpdateSignature?: Map): ReadonlyArray { - // Since the operation could be cancelled, the signatures are always stored in the cache - // They will be commited once it is safe to use them - // eg when calling this api from tsserver, if there is no cancellation of the operation - // In the other cases the affected files signatures are commited only after the iteration through the result is complete - const signatureCache = cacheToUpdateSignature || createMap(); - const sourceFile = programOfThisState.getSourceFileByPath(path); - if (!sourceFile) { - return emptyArray; - } - - if (!updateShapeSignature(programOfThisState, sourceFile, signatureCache, cancellationToken)) { - return [sourceFile]; - } - - const result = getEmitDependentFilesAffectedBy(programOfThisState, sourceFile, signatureCache, cancellationToken); - if (!cacheToUpdateSignature) { - // Commit all the signatures in the signature cache - updateSignaturesFromCache(signatureCache); - } - return result; - } - - /** - * Get all the dependencies of the sourceFile - */ - function getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray { - const compilerOptions = programOfThisState.getCompilerOptions(); - // With --out or --outFile all outputs go into single file, all files depend on each other - if (compilerOptions.outFile || compilerOptions.out) { - return getAllFileNames(programOfThisState); - } - - // If this is non module emit, or its a global file, it depends on all the source files - if (!referencedMap || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) { - return getAllFileNames(programOfThisState); - } - - // Get the references, traversing deep from the referenceMap - const seenMap = createMap(); - const queue = [sourceFile.path]; - while (queue.length) { - const path = queue.pop(); - if (!seenMap.has(path)) { - seenMap.set(path, true); - const references = referencedMap.get(path); - if (references) { - const iterator = references.keys(); - for (let { value, done } = iterator.next(); !done; { value, done } = iterator.next()) { - queue.push(value as Path); - } - } - } - } - - return flatMapIter(seenMap.keys(), path => { - const file = programOfThisState.getSourceFileByPath(path as Path); - if (file) { - return file.fileName; - } - return path; - }); - } - - /** - * Gets the names of all files from the program - */ - function getAllFileNames(programOfThisState: Program): ReadonlyArray { - if (!allFileNames) { - allFileNames = programOfThisState.getSourceFiles().map(file => file.fileName); - } - return allFileNames; - } - - /** - * Updates the signatures from the cache - * This should be called whenever it is safe to commit the state of the builder - */ - function updateSignaturesFromCache(signatureCache: Map) { - signatureCache.forEach((signature, path) => { - fileInfos.get(path).signature = signature; - hasCalledUpdateShapeSignature.set(path, true); - }); - } - - /** - * Returns if the shape of the signature has changed since last emit - * Note that it also updates the current signature as the latest signature for the file - */ - function updateShapeSignature(program: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) { - Debug.assert(!!sourceFile); - - // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate - if (hasCalledUpdateShapeSignature.has(sourceFile.path) || cacheToUpdateSignature.has(sourceFile.path)) { - return false; - } - - const info = fileInfos.get(sourceFile.path); - Debug.assert(!!info); - - const prevSignature = info.signature; - let latestSignature: string; - if (sourceFile.isDeclarationFile) { - latestSignature = sourceFile.version; - } - else { - const emitOutput = getFileEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken); - if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { - latestSignature = computeHash(emitOutput.outputFiles[0].text); - } - else { - latestSignature = prevSignature; - } - } - cacheToUpdateSignature.set(sourceFile.path, latestSignature); - - return !prevSignature || latestSignature !== prevSignature; - } - - /** - * Gets the files referenced by the the file path - */ - function getReferencedByPaths(referencedFilePath: Path) { - return mapDefinedIter(referencedMap.entries(), ([filePath, referencesInFile]) => - referencesInFile.has(referencedFilePath) ? filePath as Path : undefined - ); - } - - /** - * Gets all files of the program excluding the default library file - */ - function getAllFilesExcludingDefaultLibraryFile(program: Program, firstSourceFile: SourceFile): ReadonlyArray { - // Use cached result - if (allFilesExcludingDefaultLibraryFile) { - return allFilesExcludingDefaultLibraryFile; - } - - let result: SourceFile[]; - addSourceFile(firstSourceFile); - for (const sourceFile of program.getSourceFiles()) { - if (sourceFile !== firstSourceFile) { - addSourceFile(sourceFile); - } - } - allFilesExcludingDefaultLibraryFile = result || emptyArray; - return allFilesExcludingDefaultLibraryFile; - - function addSourceFile(sourceFile: SourceFile) { - if (!program.isSourceFileDefaultLibrary(sourceFile)) { - (result || (result = [])).push(sourceFile); - } - } - } - - /** - * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed - */ - function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) { - const compilerOptions = programOfThisState.getCompilerOptions(); - // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project, - // so returning the file itself is good enough. - if (compilerOptions && (compilerOptions.out || compilerOptions.outFile)) { - return [sourceFileWithUpdatedShape]; - } - return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); - } - - /** - * When program emits modular code, gets the files affected by the sourceFile whose shape has changed - */ - function getFilesAffectedByUpdatedShapeWhenModuleEmit(programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined) { - if (!isExternalModule(sourceFileWithUpdatedShape) && !containsOnlyAmbientModules(sourceFileWithUpdatedShape)) { - return getAllFilesExcludingDefaultLibraryFile(programOfThisState, sourceFileWithUpdatedShape); - } - - const compilerOptions = programOfThisState.getCompilerOptions(); - if (compilerOptions && (compilerOptions.isolatedModules || compilerOptions.out || compilerOptions.outFile)) { - return [sourceFileWithUpdatedShape]; - } - - // Now we need to if each file in the referencedBy list has a shape change as well. - // Because if so, its own referencedBy files need to be saved as well to make the - // emitting result consistent with files on disk. - const seenFileNamesMap = createMap(); - - // Start with the paths this file was referenced by - seenFileNamesMap.set(sourceFileWithUpdatedShape.path, sourceFileWithUpdatedShape); - const queue = getReferencedByPaths(sourceFileWithUpdatedShape.path); - while (queue.length > 0) { - const currentPath = queue.pop(); - if (!seenFileNamesMap.has(currentPath)) { - const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath); - seenFileNamesMap.set(currentPath, currentSourceFile); - if (currentSourceFile && updateShapeSignature(programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken)) { - queue.push(...getReferencedByPaths(currentPath)); - } - } - } - - // Return array of values that needs emit - return flatMapIter(seenFileNamesMap.values(), value => value); - } - } -} - -/*@internal*/ -namespace ts.BuilderState { - type ComputeHash = (data: string) => string; - /** * Gets the files affected by the path from the program */ - export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash?: ComputeHash, cacheToUpdateSignature?: Map): ReadonlyArray { + export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, cacheToUpdateSignature?: Map): ReadonlyArray { // Since the operation could be cancelled, the signatures are always stored in the cache // They will be commited once it is safe to use them // eg when calling this api from tsserver, if there is no cancellation of the operation @@ -593,7 +203,7 @@ namespace ts.BuilderState { /** * Returns if the shape of the signature has changed since last emit */ - function updateShapeSignature(state: Readonly, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash | undefined) { + function updateShapeSignature(state: Readonly, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: Map, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash) { Debug.assert(!!sourceFile); // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate @@ -612,7 +222,7 @@ namespace ts.BuilderState { else { const emitOutput = getFileEmitOutput(programOfThisState, sourceFile, /*emitOnlyDtsFiles*/ true, cancellationToken); if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) { - latestSignature = (computeHash || identity)(emitOutput.outputFiles[0].text); + latestSignature = computeHash(emitOutput.outputFiles[0].text); } else { latestSignature = prevSignature; @@ -623,6 +233,58 @@ namespace ts.BuilderState { return !prevSignature || latestSignature !== prevSignature; } + /** + * Get all the dependencies of the sourceFile + */ + export function getAllDependencies(state: BuilderState, programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray { + const compilerOptions = programOfThisState.getCompilerOptions(); + // With --out or --outFile all outputs go into single file, all files depend on each other + if (compilerOptions.outFile || compilerOptions.out) { + return getAllFileNames(state, programOfThisState); + } + + // If this is non module emit, or its a global file, it depends on all the source files + if (!state.referencedMap || (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile))) { + return getAllFileNames(state, programOfThisState); + } + + // Get the references, traversing deep from the referenceMap + const seenMap = createMap(); + const queue = [sourceFile.path]; + while (queue.length) { + const path = queue.pop(); + if (!seenMap.has(path)) { + seenMap.set(path, true); + const references = state.referencedMap.get(path); + if (references) { + const iterator = references.keys(); + for (let { value, done } = iterator.next(); !done; { value, done } = iterator.next()) { + queue.push(value as Path); + } + } + } + } + + return flatMapIter(seenMap.keys(), path => { + const file = programOfThisState.getSourceFileByPath(path as Path); + if (file) { + return file.fileName; + } + return path; + }); + } + + /** + * Gets the names of all files from the program + */ + function getAllFileNames(state: BuilderState, programOfThisState: Program): ReadonlyArray { + if (!state.allFileNames) { + const sourceFiles = programOfThisState.getSourceFiles(); + state.allFileNames = sourceFiles === emptyArray ? emptyArray : sourceFiles.map(file => file.fileName); + } + return state.allFileNames; + } + /** * Gets the files referenced by the the file path */ diff --git a/src/server/project.ts b/src/server/project.ts index 95c41eaaf44..b4e15774cfb 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -460,7 +460,7 @@ namespace ts.server { return []; } this.updateGraph(); - this.builderState = createBuilderState(this.program, this.projectService.toCanonicalFileName, this.builderState); + this.builderState = BuilderState.create(this.program, this.projectService.toCanonicalFileName, this.builderState); return mapDefined(BuilderState.getFilesAffectedBy(this.builderState, this.program, scriptInfo.path, this.cancellationToken, data => this.projectService.host.createHash(data)), sourceFile => this.shouldEmitFile(this.projectService.getScriptInfoForPath(sourceFile.path)) ? sourceFile.fileName : undefined); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 32f4b78a8c5..575c1dc07ce 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7217,7 +7217,7 @@ declare namespace ts.server { languageServiceEnabled: boolean; readonly trace?: (s: string) => void; readonly realpath?: (path: string) => string; - private builder; + private builderState; /** * Set of files names that were updated since the last call to getChangesSinceVersion. */ From dc62bb9abc28aed804b055db3c8e45b24eda336b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Dec 2017 18:55:11 -0800 Subject: [PATCH 038/172] Change builder to BuilderProgram so it is similar to operating on program --- src/compiler/builder.ts | 519 ++++++++++-------- src/compiler/tsconfig.json | 1 + src/compiler/watch.ts | 163 ++---- src/harness/unittests/builder.ts | 14 +- src/services/tsconfig.json | 1 + tests/baselines/reference/api/typescript.d.ts | 100 ++-- 6 files changed, 417 insertions(+), 381 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 110bdd5130a..21c3b9021ee 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -2,12 +2,10 @@ /*@internal*/ namespace ts { - export enum BuilderKind { - BuilderKindSemanticDiagnostics, - BuilderKindEmitAndSemanticDiagnostics - } - - interface BuilderStateWithChangedFiles extends BuilderState { + /** + * State to store the changed files, affected files and cache semantic diagnostics + */ + export interface BuilderProgramState extends BuilderState { /** * Cache of semantic diagnostics for files with their Path being the key */ @@ -37,6 +35,10 @@ namespace ts { * Already seen affected files */ seenAffectedFiles: Map | undefined; + /** + * program corresponding to this state + */ + program: Program; } function hasSameKeys(map1: ReadonlyMap | undefined, map2: ReadonlyMap | undefined) { @@ -53,8 +55,9 @@ namespace ts { /** * Create the state so that we can iterate on changedFiles/affected files */ - function createBuilderStateWithChangedFiles(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly): BuilderStateWithChangedFiles { - const state = BuilderState.create(newProgram, getCanonicalFileName, oldState) as BuilderStateWithChangedFiles; + function createBuilderProgramState(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly): BuilderProgramState { + const state = BuilderState.create(newProgram, getCanonicalFileName, oldState) as BuilderProgramState; + state.program = newProgram; const compilerOptions = newProgram.getCompilerOptions(); if (!compilerOptions.outFile && !compilerOptions.out) { state.semanticDiagnosticsPerFile = createMap>(); @@ -107,9 +110,119 @@ namespace ts { return state; } - export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindSemanticDiagnostics): SemanticDiagnosticsBuilder; - export function createBuilder(host: BuilderHost, builderKind: BuilderKind.BuilderKindEmitAndSemanticDiagnostics): EmitAndSemanticDiagnosticsBuilder; - export function createBuilder(host: BuilderHost, builderKind: BuilderKind) { + /** + * Verifies that source file is ok to be used in calls that arent handled by next + */ + function assertSourceFileOkWithoutNextAffectedCall(state: BuilderProgramState, sourceFile: SourceFile | undefined) { + Debug.assert(!sourceFile || !state.affectedFiles || state.affectedFiles[state.affectedFilesIndex - 1] !== sourceFile || !state.semanticDiagnosticsPerFile.has(sourceFile.path)); + } + + /** + * This function returns the next affected file to be processed. + * Note that until doneAffected is called it would keep reporting same result + * This is to allow the callers to be able to actually remove affected file only when the operation is complete + * eg. if during diagnostics check cancellation token ends up cancelling the request, the affected file should be retained + */ + function getNextAffectedFile(state: BuilderProgramState, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash): SourceFile | Program | undefined { + while (true) { + const { affectedFiles } = state; + if (affectedFiles) { + const { seenAffectedFiles, semanticDiagnosticsPerFile } = state; + let { affectedFilesIndex } = state; + while (affectedFilesIndex < affectedFiles.length) { + const affectedFile = affectedFiles[affectedFilesIndex]; + if (!seenAffectedFiles.has(affectedFile.path)) { + // Set the next affected file as seen and remove the cached semantic diagnostics + state.affectedFilesIndex = affectedFilesIndex; + semanticDiagnosticsPerFile.delete(affectedFile.path); + return affectedFile; + } + seenAffectedFiles.set(affectedFile.path, true); + affectedFilesIndex++; + } + + // Remove the changed file from the change set + state.changedFilesSet.delete(state.currentChangedFilePath); + state.currentChangedFilePath = undefined; + // Commit the changes in file signature + BuilderState.updateSignaturesFromCache(state, state.currentAffectedFilesSignatures); + state.currentAffectedFilesSignatures.clear(); + state.affectedFiles = undefined; + } + + // Get next changed file + const nextKey = state.changedFilesSet.keys().next(); + if (nextKey.done) { + // Done + return undefined; + } + + // With --out or --outFile all outputs go into single file + // so operations are performed directly on program, return program + const compilerOptions = state.program.getCompilerOptions(); + if (compilerOptions.outFile || compilerOptions.out) { + Debug.assert(!state.semanticDiagnosticsPerFile); + return state.program; + } + + // Get next batch of affected files + state.currentAffectedFilesSignatures = state.currentAffectedFilesSignatures || createMap(); + state.affectedFiles = BuilderState.getFilesAffectedBy(state, state.program, nextKey.value as Path, cancellationToken, computeHash, state.currentAffectedFilesSignatures); + state.currentChangedFilePath = nextKey.value as Path; + state.semanticDiagnosticsPerFile.delete(nextKey.value as Path); + state.affectedFilesIndex = 0; + state.seenAffectedFiles = state.seenAffectedFiles || createMap(); + } + } + + /** + * This is called after completing operation on the next affected file. + * The operations here are postponed to ensure that cancellation during the iteration is handled correctly + */ + function doneWithAffectedFile(state: BuilderProgramState, affected: SourceFile | Program) { + if (affected === state.program) { + state.changedFilesSet.clear(); + } + else { + state.seenAffectedFiles.set((affected as SourceFile).path, true); + state.affectedFilesIndex++; + } + } + + /** + * Returns the result with affected file + */ + function toAffectedFileResult(state: BuilderProgramState, result: T, affected: SourceFile | Program): AffectedFileResult { + doneWithAffectedFile(state, affected); + return { result, affected }; + } + + /** + * Gets the semantic diagnostics either from cache if present, or otherwise from program and caches it + * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files/changed file set + */ + function getSemanticDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { + const path = sourceFile.path; + const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path); + // Report the semantic diagnostics from the cache if we already have those diagnostics present + if (cachedDiagnostics) { + return cachedDiagnostics; + } + + // Diagnostics werent cached, get them from program, and cache the result + const diagnostics = state.program.getSemanticDiagnostics(sourceFile, cancellationToken); + state.semanticDiagnosticsPerFile.set(path, diagnostics); + return diagnostics; + } + + export enum BuilderProgramKind { + SemanticDiagnosticsBuilderProgram, + EmitAndSemanticDiagnosticsBuilderProgram + } + + export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BaseBuilderProgram | undefined, kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram): SemanticDiagnosticsBuilderProgram; + export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BaseBuilderProgram | undefined, kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram): EmitAndSemanticDiagnosticsBuilderProgram; + export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BaseBuilderProgram | undefined, kind: BuilderProgramKind) { /** * Create the canonical file name for identity */ @@ -118,183 +231,129 @@ namespace ts { * Computing hash to for signature verification */ const computeHash = host.createHash || identity; - let state: BuilderStateWithChangedFiles; + const state = createBuilderProgramState(newProgram, getCanonicalFileName, oldProgram && oldProgram.getState()); - switch (builderKind) { - case BuilderKind.BuilderKindSemanticDiagnostics: - return getSemanticDiagnosticsBuilder(); - case BuilderKind.BuilderKindEmitAndSemanticDiagnostics: - return getEmitAndSemanticDiagnosticsBuilder(); - default: - notImplemented(); + // To ensure that we arent storing any references to old program or new program without state + newProgram = undefined; + oldProgram = undefined; + + const result: BaseBuilderProgram = { + getState: () => state, + getCompilerOptions: () => state.program.getCompilerOptions(), + getSourceFile: fileName => state.program.getSourceFile(fileName), + getSourceFiles: () => state.program.getSourceFiles(), + getOptionsDiagnostics: cancellationToken => state.program.getOptionsDiagnostics(cancellationToken), + getGlobalDiagnostics: cancellationToken => state.program.getGlobalDiagnostics(cancellationToken), + getSyntacticDiagnostics: (sourceFile, cancellationToken) => state.program.getSyntacticDiagnostics(sourceFile, cancellationToken), + getSemanticDiagnostics, + emit, + getAllDependencies: sourceFile => BuilderState.getAllDependencies(state, state.program, sourceFile) + }; + + if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) { + (result as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; + } + else if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { + (result as EmitAndSemanticDiagnosticsBuilderProgram).getCurrentDirectory = () => state.program.getCurrentDirectory(); + (result as EmitAndSemanticDiagnosticsBuilderProgram).emitNextAffectedFile = emitNextAffectedFile; + } + else { + notImplemented(); } - function getSemanticDiagnosticsBuilder(): SemanticDiagnosticsBuilder { - return { - updateProgram, - getAllDependencies, - getSemanticDiagnosticsOfNextAffectedFile, - getSemanticDiagnostics - }; - } - - function getEmitAndSemanticDiagnosticsBuilder(): EmitAndSemanticDiagnosticsBuilder { - return { - updateProgram, - getAllDependencies, - emitNextAffectedFile, - getSemanticDiagnostics - }; - } + return result; /** - * Update current state to reflect new program - * Updates changed files, references, file infos etc which happens through the state callbacks + * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files */ - function updateProgram(newProgram: Program) { - state = createBuilderStateWithChangedFiles(newProgram, getCanonicalFileName, state); - } - - /** - * This function returns the next affected file to be processed. - * Note that until doneAffected is called it would keep reporting same result - * This is to allow the callers to be able to actually remove affected file only when the operation is complete - * eg. if during diagnostics check cancellation token ends up cancelling the request, the affected file should be retained - */ - function getNextAffectedFile(programOfThisState: Program, cancellationToken: CancellationToken | undefined): SourceFile | Program | undefined { - while (true) { - const { affectedFiles } = state; - if (affectedFiles) { - const { seenAffectedFiles, semanticDiagnosticsPerFile } = state; - let { affectedFilesIndex } = state; - while (affectedFilesIndex < affectedFiles.length) { - const affectedFile = affectedFiles[affectedFilesIndex]; - if (!seenAffectedFiles.has(affectedFile.path)) { - // Set the next affected file as seen and remove the cached semantic diagnostics - state.affectedFilesIndex = affectedFilesIndex; - semanticDiagnosticsPerFile.delete(affectedFile.path); - return affectedFile; - } - seenAffectedFiles.set(affectedFile.path, true); - affectedFilesIndex++; - } - - // Remove the changed file from the change set - state.changedFilesSet.delete(state.currentChangedFilePath); - state.currentChangedFilePath = undefined; - // Commit the changes in file signature - BuilderState.updateSignaturesFromCache(state, state.currentAffectedFilesSignatures); - state.currentAffectedFilesSignatures.clear(); - state.affectedFiles = undefined; - } - - // Get next changed file - const nextKey = state.changedFilesSet.keys().next(); - if (nextKey.done) { - // Done - return undefined; - } - - const compilerOptions = programOfThisState.getCompilerOptions(); - // With --out or --outFile all outputs go into single file - // so operations are performed directly on program, return program - if (compilerOptions.outFile || compilerOptions.out) { - Debug.assert(!state.semanticDiagnosticsPerFile); - return programOfThisState; - } - - // Get next batch of affected files - state.currentAffectedFilesSignatures = state.currentAffectedFilesSignatures || createMap(); - state.affectedFiles = BuilderState.getFilesAffectedBy(state, programOfThisState, nextKey.value as Path, cancellationToken, computeHash, state.currentAffectedFilesSignatures); - state.currentChangedFilePath = nextKey.value as Path; - state.semanticDiagnosticsPerFile.delete(nextKey.value as Path); - state.affectedFilesIndex = 0; - state.seenAffectedFiles = state.seenAffectedFiles || createMap(); - } - } - - /** - * This is called after completing operation on the next affected file. - * The operations here are postponed to ensure that cancellation during the iteration is handled correctly - */ - function doneWithAffectedFile(programOfThisState: Program, affected: SourceFile | Program) { - if (affected === programOfThisState) { - state.changedFilesSet.clear(); - } - else { - state.seenAffectedFiles.set((affected as SourceFile).path, true); - state.affectedFilesIndex++; - } - } - - /** - * Returns the result with affected file - */ - function toAffectedFileResult(programOfThisState: Program, result: T, affected: SourceFile | Program): AffectedFileResult { - doneWithAffectedFile(programOfThisState, affected); - return { result, affected }; - } - - /** - * Emits the next affected file, and returns the EmitResult along with source files emitted - * Returns undefined when iteration is complete - */ - function emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult { - const affectedFile = getNextAffectedFile(programOfThisState, cancellationToken); - if (!affectedFile) { + function emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult { + const affected = getNextAffectedFile(state, cancellationToken, computeHash); + if (!affected) { // Done return undefined; } - else if (affectedFile === programOfThisState) { - // When whole program is affected, do emit only once (eg when --out or --outFile is specified) - return toAffectedFileResult( - programOfThisState, - programOfThisState.emit(/*targetSourceFile*/ undefined, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers), - programOfThisState - ); - } - // Emit the affected file - const targetSourceFile = affectedFile as SourceFile; return toAffectedFileResult( - programOfThisState, - programOfThisState.emit(targetSourceFile, writeFileCallback, cancellationToken, /*emitOnlyDtsFiles*/ false, customTransformers), - targetSourceFile + state, + // When whole program is affected, do emit only once (eg when --out or --outFile is specified) + // Otherwise just affected file + state.program.emit(affected === state.program ? undefined : affected as SourceFile, writeFile || host.writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers), + affected ); } + /** + * Emits the JavaScript and declaration files. + * When targetSource file is specified, emits the files corresponding to that source file, + * otherwise for the whole program. + * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, + * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, + * it will only emit all the affected files instead of whole program + * + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files + */ + function emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult { + if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { + assertSourceFileOkWithoutNextAffectedCall(state, targetSourceFile); + if (!targetSourceFile) { + // Emit and report any errors we ran into. + let sourceMaps: SourceMapData[] = []; + let emitSkipped: boolean; + let diagnostics: Diagnostic[]; + let emittedFiles: string[] = []; + + let affectedEmitResult: AffectedFileResult; + while (affectedEmitResult = emitNextAffectedFile(writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers)) { + emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped; + diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics); + emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles); + sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps); + } + return { + emitSkipped, + diagnostics: diagnostics || emptyArray, + emittedFiles, + sourceMaps + }; + } + } + return state.program.emit(targetSourceFile, writeFile || host.writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); + } + /** * Return the semantic diagnostics for the next affected file or undefined if iteration is complete * If provided ignoreSourceFile would be called before getting the diagnostics and would ignore the sourceFile if the returned value was true */ - function getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult> { + function getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult> { while (true) { - const affectedFile = getNextAffectedFile(programOfThisState, cancellationToken); - if (!affectedFile) { + const affected = getNextAffectedFile(state, cancellationToken, computeHash); + if (!affected) { // Done return undefined; } - else if (affectedFile === programOfThisState) { + else if (affected === state.program) { // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified) return toAffectedFileResult( - programOfThisState, - programOfThisState.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken), - programOfThisState + state, + state.program.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken), + affected ); } // Get diagnostics for the affected file if its not ignored - const targetSourceFile = affectedFile as SourceFile; - if (ignoreSourceFile && ignoreSourceFile(targetSourceFile)) { + if (ignoreSourceFile && ignoreSourceFile(affected as SourceFile)) { // Get next affected file - doneWithAffectedFile(programOfThisState, targetSourceFile); + doneWithAffectedFile(state, affected); continue; } return toAffectedFileResult( - programOfThisState, - getSemanticDiagnosticsOfFile(programOfThisState, targetSourceFile, cancellationToken), - targetSourceFile + state, + getSemanticDiagnosticsOfFile(state, affected as SourceFile, cancellationToken), + affected ); } } @@ -302,59 +361,46 @@ namespace ts { /** * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program * The semantic diagnostics are cached and managed here - * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files + * Note that it is assumed that when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics + * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, + * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics */ - function getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { - Debug.assert(!state.affectedFiles || state.affectedFiles[state.affectedFilesIndex - 1] !== sourceFile || !state.semanticDiagnosticsPerFile.has(sourceFile.path)); - const compilerOptions = programOfThisState.getCompilerOptions(); + function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { + assertSourceFileOkWithoutNextAffectedCall(state, sourceFile); + const compilerOptions = state.program.getCompilerOptions(); if (compilerOptions.outFile || compilerOptions.out) { Debug.assert(!state.semanticDiagnosticsPerFile); // We dont need to cache the diagnostics just return them from program - return programOfThisState.getSemanticDiagnostics(sourceFile, cancellationToken); + return state.program.getSemanticDiagnostics(sourceFile, cancellationToken); } if (sourceFile) { - return getSemanticDiagnosticsOfFile(programOfThisState, sourceFile, cancellationToken); + return getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken); + } + + if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) { + // When semantic builder asks for diagnostics of the whole program, + // ensure that all the affected files are handled + let affected: SourceFile | Program | undefined; + while (affected = getNextAffectedFile(state, cancellationToken, computeHash)) { + doneWithAffectedFile(state, affected); + } } let diagnostics: Diagnostic[]; - for (const sourceFile of programOfThisState.getSourceFiles()) { - diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(programOfThisState, sourceFile, cancellationToken)); + for (const sourceFile of state.program.getSourceFiles()) { + diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken)); } return diagnostics || emptyArray; } - - /** - * Gets the semantic diagnostics either from cache if present, or otherwise from program and caches it - * Note that it is assumed that the when asked about semantic diagnostics, the file has been taken out of affected files/changed file set - */ - function getSemanticDiagnosticsOfFile(program: Program, sourceFile: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { - const path = sourceFile.path; - const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path); - // Report the semantic diagnostics from the cache if we already have those diagnostics present - if (cachedDiagnostics) { - return cachedDiagnostics; - } - - // Diagnostics werent cached, get them from program, and cache the result - const diagnostics = program.getSemanticDiagnostics(sourceFile, cancellationToken); - state.semanticDiagnosticsPerFile.set(path, diagnostics); - return diagnostics; - } - - /** - * Get all the dependencies of the sourceFile - */ - function getAllDependencies(programOfThisState: Program, sourceFile: SourceFile) { - return BuilderState.getAllDependencies(state, programOfThisState, sourceFile); - } } } namespace ts { export type AffectedFileResult = { result: T; affected: SourceFile | Program; } | undefined; - export interface BuilderHost { + export interface BuilderProgramHost { /** * return true if file names are treated with case sensitivity */ @@ -363,73 +409,110 @@ namespace ts { * If provided this would be used this hash instead of actual file shape text for detecting changes */ createHash?: (data: string) => string; + /** + * When emit or emitNextAffectedFile are called without writeFile, + * this callback if present would be used to write files + */ + writeFile?: WriteFileCallback; } /** * Builder to manage the program state changes */ - export interface BaseBuilder { + export interface BaseBuilderProgram { + /*@internal*/ + getState(): BuilderProgramState; /** - * Updates the program in the builder to represent new state + * Get compiler options of the program */ - updateProgram(newProgram: Program): void; - + getCompilerOptions(): CompilerOptions; + /** + * Get the source file in the program with file name + */ + getSourceFile(fileName: string): SourceFile | undefined; + /** + * Get a list of files in the program + */ + getSourceFiles(): ReadonlyArray; + /** + * Get the diagnostics for compiler options + */ + getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get the diagnostics that dont belong to any file + */ + getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get the syntax diagnostics, for all source files if source file is not supplied + */ + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; /** * Get all the dependencies of the file */ - getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray; + getAllDependencies(sourceFile: SourceFile): ReadonlyArray; + /** + * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program + * The semantic diagnostics are cached and managed here + * Note that it is assumed that when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics + * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, + * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics + */ + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Emits the JavaScript and declaration files. + * When targetSource file is specified, emits the files corresponding to that source file, + * otherwise for the whole program. + * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, + * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, + * it will only emit all the affected files instead of whole program + * + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files + */ + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; } /** * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files */ - export interface SemanticDiagnosticsBuilder extends BaseBuilder { + export interface SemanticDiagnosticsBuilderProgram extends BaseBuilderProgram { /** * Gets the semantic diagnostics from the program for the next affected file and caches it * Returns undefined if the iteration is complete */ - getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; - - /** - * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program - * The semantic diagnostics are cached and managed here - * Note that it is assumed that the when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - */ - getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; } /** * The builder that can handle the changes in program and iterate through changed file to emit the files * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files */ - export interface EmitAndSemanticDiagnosticsBuilder extends BaseBuilder { + export interface EmitAndSemanticDiagnosticsBuilderProgram extends BaseBuilderProgram { + /** + * Get the current directory of the program + */ + getCurrentDirectory(): string; /** * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files */ - emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult; - - /** - * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program - * The semantic diagnostics are cached and managed here - * Note that it is assumed that the when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - */ - getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult; } /** * Create the builder to manage semantic diagnostics and cache them */ - export function createSemanticDiagnosticsBuilder(host: BuilderHost): SemanticDiagnosticsBuilder { - return createBuilder(host, BuilderKind.BuilderKindSemanticDiagnostics); + export function createSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: SemanticDiagnosticsBuilderProgram): SemanticDiagnosticsBuilderProgram { + return createBuilderProgram(newProgram, host, oldProgram, BuilderProgramKind.SemanticDiagnosticsBuilderProgram); } /** * Create the builder that can handle the changes in program and iterate through changed files * to emit the those files and manage semantic diagnostics cache as well */ - export function createEmitAndSemanticDiagnosticsBuilder(host: BuilderHost): EmitAndSemanticDiagnosticsBuilder { - return createBuilder(host, BuilderKind.BuilderKindEmitAndSemanticDiagnostics); + export function createEmitAndSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram): EmitAndSemanticDiagnosticsBuilderProgram { + return createBuilderProgram(newProgram, host, oldProgram, BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram); } } diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index 07f69ddfe28..92d9f099441 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -38,6 +38,7 @@ "emitter.ts", "watchUtilities.ts", "program.ts", + "builderState.ts", "builder.ts", "resolutionCache.ts", "watch.ts", diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 1c5a0ad5d99..c07722cb7d3 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -146,62 +146,23 @@ namespace ts { return ExitStatus.Success; } - /** - * Creates the function that emits files and reports errors when called with program - */ - function createEmitFilesAndReportErrorsWithBuilderUsingSystem(system: System, reportDiagnostic: DiagnosticReporter) { - const emitErrorsAndReportErrorsWithBuilder = createEmitFilesAndReportErrorsWithBuilder({ - useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, - createHash: system.createHash && (s => system.createHash(s)), - writeFile, - reportDiagnostic, - writeFileName: s => system.write(s + system.newLine) - }); - let host: CachedDirectoryStructureHost | undefined; - return { - emitFilesAndReportError: (program: Program) => emitErrorsAndReportErrorsWithBuilder(program), - setHost: (cachedDirectoryStructureHost: CachedDirectoryStructureHost) => host = cachedDirectoryStructureHost - }; - - function getHost() { - return host || system; - } - - function ensureDirectoriesExist(directoryPath: string) { - if (directoryPath.length > getRootLength(directoryPath) && !getHost().directoryExists(directoryPath)) { - const parentDirectory = getDirectoryPath(directoryPath); - ensureDirectoriesExist(parentDirectory); - getHost().createDirectory(directoryPath); - } - } - - function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, onError: (message: string) => void) { - try { - performance.mark("beforeIOWrite"); - ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - - getHost().writeFile(fileName, text, writeByteOrderMark); - - performance.mark("afterIOWrite"); - performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); - } - catch (e) { - if (onError) { - onError(e.message); - } - } - } - } - const noopFileWatcher: FileWatcher = { close: noop }; /** * Creates the watch compiler host that can be extended with config file or root file names and options host */ function createWatchCompilerHost(system = sys, reportDiagnostic: DiagnosticReporter): WatchCompilerHost { - const { emitFilesAndReportError, setHost } = createEmitFilesAndReportErrorsWithBuilderUsingSystem(system, reportDiagnostic); - const host: WatchCompilerHost = { - useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, + let host: DirectoryStructureHost = system; + const useCaseSensitiveFileNames = () => system.useCaseSensitiveFileNames; + const writeFileName = (s: string) => system.write(s + system.newLine); + const builderProgramHost: BuilderProgramHost = { + useCaseSensitiveFileNames, + createHash: system.createHash && (s => system.createHash(s)), + writeFile + }; + let builderProgram: EmitAndSemanticDiagnosticsBuilderProgram | undefined; + return { + useCaseSensitiveFileNames, getNewLine: () => system.newLine, getCurrentDirectory: () => system.getCurrentDirectory(), getDefaultLibLocation, @@ -220,10 +181,9 @@ namespace ts { onWatchStatusChange, createDirectory: path => system.createDirectory(path), writeFile: (path, data, writeByteOrderMark) => system.writeFile(path, data, writeByteOrderMark), - onCachedDirectoryStructureHostCreate: host => setHost(host), - afterProgramCreate: emitFilesAndReportError, + onCachedDirectoryStructureHostCreate: cacheHost => host = cacheHost || system, + afterProgramCreate: emitFilesAndReportErrorUsingBuilder, }; - return host; function getDefaultLibLocation() { return getDirectoryPath(normalizePath(system.getExecutingFilePath())); @@ -235,6 +195,36 @@ namespace ts { } system.write(`${new Date().toLocaleTimeString()} - ${flattenDiagnosticMessageText(diagnostic.messageText, newLine)}${newLine + newLine + newLine}`); } + + function emitFilesAndReportErrorUsingBuilder(program: Program) { + builderProgram = createEmitAndSemanticDiagnosticsBuilderProgram(program, builderProgramHost, builderProgram); + emitFilesAndReportErrors(builderProgram, reportDiagnostic, writeFileName); + } + + function ensureDirectoriesExist(directoryPath: string) { + if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists(directoryPath)) { + const parentDirectory = getDirectoryPath(directoryPath); + ensureDirectoriesExist(parentDirectory); + host.createDirectory(directoryPath); + } + } + + function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, onError: (message: string) => void) { + try { + performance.mark("beforeIOWrite"); + ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); + + host.writeFile(fileName, text, writeByteOrderMark); + + performance.mark("afterIOWrite"); + performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); + } + catch (e) { + if (onError) { + onError(e.message); + } + } + } } /** @@ -272,73 +262,6 @@ namespace ts { namespace ts { export type DiagnosticReporter = (diagnostic: Diagnostic) => void; - interface BuilderProgram extends ProgramToEmitFilesAndReportErrors { - updateProgram(program: Program): void; - } - - function createBuilderProgram(host: BuilderEmitHost): BuilderProgram { - const builder = createEmitAndSemanticDiagnosticsBuilder(host); - let program: Program; - return { - getCurrentDirectory: () => program.getCurrentDirectory(), - getCompilerOptions: () => program.getCompilerOptions(), - getSourceFiles: () => program.getSourceFiles(), - getSyntacticDiagnostics: () => program.getSyntacticDiagnostics(), - getOptionsDiagnostics: () => program.getOptionsDiagnostics(), - getGlobalDiagnostics: () => program.getGlobalDiagnostics(), - getSemanticDiagnostics: () => builder.getSemanticDiagnostics(program), - emit, - updateProgram - }; - - function updateProgram(p: Program) { - program = p; - builder.updateProgram(p); - } - - function emit(): EmitResult { - // Emit and report any errors we ran into. - let sourceMaps: SourceMapData[]; - let emitSkipped: boolean; - let diagnostics: Diagnostic[]; - let emittedFiles: string[]; - - let affectedEmitResult: AffectedFileResult; - while (affectedEmitResult = builder.emitNextAffectedFile(program, host.writeFile)) { - emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped; - diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics); - emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles); - sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps); - } - return { - emitSkipped, - diagnostics, - emittedFiles, - sourceMaps - }; - } - } - - /** - * Host needed to emit files and report errors using builder - */ - export interface BuilderEmitHost extends BuilderHost { - writeFile: WriteFileCallback; - reportDiagnostic: DiagnosticReporter; - writeFileName?: (s: string) => void; - } - - /** - * Creates the function that reports the program errors and emit files every time it is called with argument as program - */ - export function createEmitFilesAndReportErrorsWithBuilder(host: BuilderEmitHost) { - const builderProgram = createBuilderProgram(host); - return (program: Program) => { - builderProgram.updateProgram(program); - emitFilesAndReportErrors(builderProgram, host.reportDiagnostic, host.writeFileName); - }; - } - export interface WatchCompilerHost { /** If provided, callback to invoke before each program creation */ beforeProgramCreate?(compilerOptions: CompilerOptions): void; diff --git a/src/harness/unittests/builder.ts b/src/harness/unittests/builder.ts index a6353d9c0c3..8808a151c04 100644 --- a/src/harness/unittests/builder.ts +++ b/src/harness/unittests/builder.ts @@ -81,20 +81,22 @@ namespace ts { }); function makeAssertChanges(getProgram: () => Program): (fileNames: ReadonlyArray) => void { - const builder = createEmitAndSemanticDiagnosticsBuilder({ useCaseSensitiveFileNames: returnTrue, }); + const host: BuilderProgramHost = { useCaseSensitiveFileNames: returnTrue }; + let builderProgram: EmitAndSemanticDiagnosticsBuilderProgram | undefined; return fileNames => { const program = getProgram(); - builder.updateProgram(program); + builderProgram = createEmitAndSemanticDiagnosticsBuilderProgram(program, host, builderProgram); const outputFileNames: string[] = []; // tslint:disable-next-line no-empty - while (builder.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName))) { + while (builderProgram.emitNextAffectedFile(fileName => outputFileNames.push(fileName))) { } assert.deepEqual(outputFileNames, fileNames); }; } function makeAssertChangesWithCancellationToken(getProgram: () => Program): (fileNames: ReadonlyArray, cancelAfterEmitLength?: number) => void { - const builder = createEmitAndSemanticDiagnosticsBuilder({ useCaseSensitiveFileNames: returnTrue, }); + const host: BuilderProgramHost = { useCaseSensitiveFileNames: returnTrue }; + let builderProgram: EmitAndSemanticDiagnosticsBuilderProgram | undefined; let cancel = false; const cancellationToken: CancellationToken = { isCancellationRequested: () => cancel, @@ -108,7 +110,7 @@ namespace ts { cancel = false; let operationWasCancelled = false; const program = getProgram(); - builder.updateProgram(program); + builderProgram = createEmitAndSemanticDiagnosticsBuilderProgram(program, host, builderProgram); const outputFileNames: string[] = []; try { // tslint:disable-next-line no-empty @@ -117,7 +119,7 @@ namespace ts { if (outputFileNames.length === cancelAfterEmitLength) { cancel = true; } - } while (builder.emitNextAffectedFile(program, fileName => outputFileNames.push(fileName), cancellationToken)); + } while (builderProgram.emitNextAffectedFile(fileName => outputFileNames.push(fileName), cancellationToken)); } catch (e) { assert.isFalse(operationWasCancelled); diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index ba944bafd9d..13a7a30d845 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -37,6 +37,7 @@ "../compiler/declarationEmitter.ts", "../compiler/emitter.ts", "../compiler/program.ts", + "../compiler/builderState.ts", "../compiler/builder.ts", "../compiler/resolutionCache.ts", "../compiler/watch.ts", diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 514c140f05e..59f3916bbfe 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3749,7 +3749,7 @@ declare namespace ts { result: T; affected: SourceFile | Program; } | undefined; - interface BuilderHost { + interface BuilderProgramHost { /** * return true if file names are treated with case sensitivity */ @@ -3758,78 +3758,104 @@ declare namespace ts { * If provided this would be used this hash instead of actual file shape text for detecting changes */ createHash?: (data: string) => string; + /** + * When emit or emitNextAffectedFile are called without writeFile, + * this callback if present would be used to write files + */ + writeFile?: WriteFileCallback; } /** * Builder to manage the program state changes */ - interface BaseBuilder { + interface BaseBuilderProgram { /** - * Updates the program in the builder to represent new state + * Get compiler options of the program */ - updateProgram(newProgram: Program): void; + getCompilerOptions(): CompilerOptions; + /** + * Get the source file in the program with file name + */ + getSourceFile(fileName: string): SourceFile | undefined; + /** + * Get a list of files in the program + */ + getSourceFiles(): ReadonlyArray; + /** + * Get the diagnostics for compiler options + */ + getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get the diagnostics that dont belong to any file + */ + getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Get the syntax diagnostics, for all source files if source file is not supplied + */ + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; /** * Get all the dependencies of the file */ - getAllDependencies(programOfThisState: Program, sourceFile: SourceFile): ReadonlyArray; + getAllDependencies(sourceFile: SourceFile): ReadonlyArray; + /** + * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program + * The semantic diagnostics are cached and managed here + * Note that it is assumed that when asked about semantic diagnostics through this API, + * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics + * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, + * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics + */ + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + /** + * Emits the JavaScript and declaration files. + * When targetSource file is specified, emits the files corresponding to that source file, + * otherwise for the whole program. + * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, + * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, + * it will only emit all the affected files instead of whole program + * + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files + */ + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; } /** * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files */ - interface SemanticDiagnosticsBuilder extends BaseBuilder { + interface SemanticDiagnosticsBuilderProgram extends BaseBuilderProgram { /** * Gets the semantic diagnostics from the program for the next affected file and caches it * Returns undefined if the iteration is complete */ - getSemanticDiagnosticsOfNextAffectedFile(programOfThisState: Program, cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; - /** - * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program - * The semantic diagnostics are cached and managed here - * Note that it is assumed that the when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - */ - getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; } /** * The builder that can handle the changes in program and iterate through changed file to emit the files * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files */ - interface EmitAndSemanticDiagnosticsBuilder extends BaseBuilder { + interface EmitAndSemanticDiagnosticsBuilderProgram extends BaseBuilderProgram { + /** + * Get the current directory of the program + */ + getCurrentDirectory(): string; /** * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete + * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host + * in that order would be used to write the files */ - emitNextAffectedFile(programOfThisState: Program, writeFileCallback: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult; - /** - * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program - * The semantic diagnostics are cached and managed here - * Note that it is assumed that the when asked about semantic diagnostics through this API, - * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics - */ - getSemanticDiagnostics(programOfThisState: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult; } /** * Create the builder to manage semantic diagnostics and cache them */ - function createSemanticDiagnosticsBuilder(host: BuilderHost): SemanticDiagnosticsBuilder; + function createSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: SemanticDiagnosticsBuilderProgram): SemanticDiagnosticsBuilderProgram; /** * Create the builder that can handle the changes in program and iterate through changed files * to emit the those files and manage semantic diagnostics cache as well */ - function createEmitAndSemanticDiagnosticsBuilder(host: BuilderHost): EmitAndSemanticDiagnosticsBuilder; + function createEmitAndSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: EmitAndSemanticDiagnosticsBuilderProgram): EmitAndSemanticDiagnosticsBuilderProgram; } declare namespace ts { type DiagnosticReporter = (diagnostic: Diagnostic) => void; - /** - * Host needed to emit files and report errors using builder - */ - interface BuilderEmitHost extends BuilderHost { - writeFile: WriteFileCallback; - reportDiagnostic: DiagnosticReporter; - writeFileName?: (s: string) => void; - } - /** - * Creates the function that reports the program errors and emit files every time it is called with argument as program - */ - function createEmitFilesAndReportErrorsWithBuilder(host: BuilderEmitHost): (program: Program) => void; interface WatchCompilerHost { /** If provided, callback to invoke before each program creation */ beforeProgramCreate?(compilerOptions: CompilerOptions): void; From 9b54d2e458248dfdd4dc5ff0078ccf9f56a2719b Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Dec 2017 19:20:05 -0800 Subject: [PATCH 039/172] Create api to create Watch --- src/compiler/builder.ts | 11 ++++- src/compiler/tsc.ts | 4 +- src/compiler/watch.ts | 42 +++++++++++++------ tests/baselines/reference/api/typescript.d.ts | 24 +++++++---- 4 files changed, 58 insertions(+), 23 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 21c3b9021ee..d0a6c99179e 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -223,6 +223,14 @@ namespace ts { export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BaseBuilderProgram | undefined, kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram): SemanticDiagnosticsBuilderProgram; export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BaseBuilderProgram | undefined, kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram): EmitAndSemanticDiagnosticsBuilderProgram; export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BaseBuilderProgram | undefined, kind: BuilderProgramKind) { + // Return same program if underlying program doesnt change + let oldState = oldProgram && oldProgram.getState(); + if (oldState && newProgram === oldState.program) { + newProgram = undefined; + oldState = undefined; + return oldProgram; + } + /** * Create the canonical file name for identity */ @@ -231,11 +239,12 @@ namespace ts { * Computing hash to for signature verification */ const computeHash = host.createHash || identity; - const state = createBuilderProgramState(newProgram, getCanonicalFileName, oldProgram && oldProgram.getState()); + const state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState); // To ensure that we arent storing any references to old program or new program without state newProgram = undefined; oldProgram = undefined; + oldState = undefined; const result: BaseBuilderProgram = { getState: () => state, diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index ed5284e696c..ec869f89ce2 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -165,13 +165,13 @@ namespace ts { watchCompilerHost.options = configParseResult.options; watchCompilerHost.configFileSpecs = configParseResult.configFileSpecs; watchCompilerHost.configFileWildCardDirectories = configParseResult.wildcardDirectories; - createWatch(watchCompilerHost); + createWatchProgram(watchCompilerHost); } function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions) { const watchCompilerHost = ts.createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, sys, reportDiagnostic); updateWatchCompilationHost(watchCompilerHost); - createWatch(watchCompilerHost); + createWatchProgram(watchCompilerHost); } function enableStatistics(compilerOptions: CompilerOptions) { diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index c07722cb7d3..0b197c6a0f6 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -376,24 +376,24 @@ namespace ts { configFileWildCardDirectories?: MapLike; } - export interface Watch { + export interface Watch { /** Synchronize with host and get updated program */ - getProgram(): Program; + getProgram(): T; /** Gets the existing program without synchronizing with changes on host */ /*@internal*/ - getExistingProgram(): Program; + getExistingProgram(): T; } /** * Creates the watch what generates program using the config file */ - export interface WatchOfConfigFile extends Watch { + export interface WatchOfConfigFile extends Watch { } /** * Creates the watch that generates program using the root files and compiler options */ - export interface WatchOfFilesAndCompilerOptions extends Watch { + export interface WatchOfFilesAndCompilerOptions extends Watch { /** Updates the root files in the program, only if this is not config file compilation */ updateRootFileNames(fileNames: string[]): void; } @@ -401,26 +401,26 @@ namespace ts { /** * Create the watched program for config file */ - export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { - return createWatch(createWatchCompilerHostOfConfigFile(configFileName, optionsToExtend, system, reportDiagnostic)); + export function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile { + return createWatchProgram(createWatchCompilerHostOfConfigFile(configFileName, optionsToExtend, system, reportDiagnostic)); } /** * Create the watched program for root files and compiler options */ - export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfFilesAndCompilerOptions { - return createWatch(createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, system, reportDiagnostic)); + export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system = sys, reportDiagnostic?: DiagnosticReporter): WatchOfFilesAndCompilerOptions { + return createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, system, reportDiagnostic)); } /** * Creates the watch from the host for root files and compiler options */ - export function createWatch(host: WatchCompilerHostOfFilesAndCompilerOptions): WatchOfFilesAndCompilerOptions; + export function createWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions): WatchOfFilesAndCompilerOptions; /** * Creates the watch from the host for config file */ - export function createWatch(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; - export function createWatch(host: WatchCompilerHostOfFilesAndCompilerOptions & WatchCompilerHostOfConfigFile): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { + export function createWatchProgram(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; + export function createWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & WatchCompilerHostOfConfigFile): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { interface HostFileInfo { version: number; sourceFile: SourceFile; @@ -890,4 +890,22 @@ namespace ts { ); } } + + /** + * Creates the watch from the host for root files and compiler options + */ + export function createBuilderWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfFilesAndCompilerOptions; + /** + * Creates the watch from the host for config file + */ + export function createBuilderWatchProgram(host: WatchCompilerHostOfConfigFile & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfConfigFile; + export function createBuilderWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & WatchCompilerHostOfConfigFile & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { + const watch = createWatchProgram(host); + let builderProgram: T | undefined; + return { + getProgram: () => builderProgram = createBuilderProgram(watch.getProgram(), host, builderProgram), + getExistingProgram: () => builderProgram = createBuilderProgram(watch.getExistingProgram(), host, builderProgram), + updateRootFileNames: watch.updateRootFileNames && (fileNames => watch.updateRootFileNames(fileNames)) + }; + } } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 59f3916bbfe..9544057047a 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3935,38 +3935,46 @@ declare namespace ts { */ readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; } - interface Watch { + interface Watch { /** Synchronize with host and get updated program */ - getProgram(): Program; + getProgram(): T; } /** * Creates the watch what generates program using the config file */ - interface WatchOfConfigFile extends Watch { + interface WatchOfConfigFile extends Watch { } /** * Creates the watch that generates program using the root files and compiler options */ - interface WatchOfFilesAndCompilerOptions extends Watch { + interface WatchOfFilesAndCompilerOptions extends Watch { /** Updates the root files in the program, only if this is not config file compilation */ updateRootFileNames(fileNames: string[]): void; } /** * Create the watched program for config file */ - function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile; + function createWatchOfConfigFile(configFileName: string, optionsToExtend?: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfConfigFile; /** * Create the watched program for root files and compiler options */ - function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfFilesAndCompilerOptions; + function createWatchOfFilesAndCompilerOptions(rootFiles: string[], options: CompilerOptions, system?: System, reportDiagnostic?: DiagnosticReporter): WatchOfFilesAndCompilerOptions; /** * Creates the watch from the host for root files and compiler options */ - function createWatch(host: WatchCompilerHostOfFilesAndCompilerOptions): WatchOfFilesAndCompilerOptions; + function createWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions): WatchOfFilesAndCompilerOptions; /** * Creates the watch from the host for config file */ - function createWatch(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; + function createWatchProgram(host: WatchCompilerHostOfConfigFile): WatchOfConfigFile; + /** + * Creates the watch from the host for root files and compiler options + */ + function createBuilderWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfFilesAndCompilerOptions; + /** + * Creates the watch from the host for config file + */ + function createBuilderWatchProgram(host: WatchCompilerHostOfConfigFile & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfConfigFile; } declare namespace ts { function parseCommandLine(commandLine: ReadonlyArray, readFile?: (path: string) => string | undefined): ParsedCommandLine; From 8ad9a6254c24742641df25b0d2c33a64888c0c77 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Dec 2017 19:44:47 -0800 Subject: [PATCH 040/172] Api to get underlying program from builder --- src/compiler/builder.ts | 5 +++++ tests/baselines/reference/api/typescript.d.ts | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index d0a6c99179e..fdbfe32d450 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -248,6 +248,7 @@ namespace ts { const result: BaseBuilderProgram = { getState: () => state, + getProgram: () => state.program, getCompilerOptions: () => state.program.getCompilerOptions(), getSourceFile: fileName => state.program.getSourceFile(fileName), getSourceFiles: () => state.program.getSourceFiles(), @@ -431,6 +432,10 @@ namespace ts { export interface BaseBuilderProgram { /*@internal*/ getState(): BuilderProgramState; + /** + * Returns current program + */ + getProgram(): Program; /** * Get compiler options of the program */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 9544057047a..8014fe09702 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3768,6 +3768,10 @@ declare namespace ts { * Builder to manage the program state changes */ interface BaseBuilderProgram { + /** + * Returns current program + */ + getProgram(): Program; /** * Get compiler options of the program */ From a75badfd11c4d5fdca27142ce73c0935f4937221 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 7 Dec 2017 19:56:46 -0800 Subject: [PATCH 041/172] Rename on WatchBuilderProgram --- src/compiler/builder.ts | 14 +++++++------- src/compiler/watch.ts | 6 +++--- tests/baselines/reference/api/typescript.d.ts | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index fdbfe32d450..13c5a4a2ca4 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -220,9 +220,9 @@ namespace ts { EmitAndSemanticDiagnosticsBuilderProgram } - export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BaseBuilderProgram | undefined, kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram): SemanticDiagnosticsBuilderProgram; - export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BaseBuilderProgram | undefined, kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram): EmitAndSemanticDiagnosticsBuilderProgram; - export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BaseBuilderProgram | undefined, kind: BuilderProgramKind) { + export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BuilderProgram | undefined, kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram): SemanticDiagnosticsBuilderProgram; + export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BuilderProgram | undefined, kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram): EmitAndSemanticDiagnosticsBuilderProgram; + export function createBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram: BuilderProgram | undefined, kind: BuilderProgramKind) { // Return same program if underlying program doesnt change let oldState = oldProgram && oldProgram.getState(); if (oldState && newProgram === oldState.program) { @@ -246,7 +246,7 @@ namespace ts { oldProgram = undefined; oldState = undefined; - const result: BaseBuilderProgram = { + const result: BuilderProgram = { getState: () => state, getProgram: () => state.program, getCompilerOptions: () => state.program.getCompilerOptions(), @@ -429,7 +429,7 @@ namespace ts { /** * Builder to manage the program state changes */ - export interface BaseBuilderProgram { + export interface BuilderProgram { /*@internal*/ getState(): BuilderProgramState; /** @@ -490,7 +490,7 @@ namespace ts { /** * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files */ - export interface SemanticDiagnosticsBuilderProgram extends BaseBuilderProgram { + export interface SemanticDiagnosticsBuilderProgram extends BuilderProgram { /** * Gets the semantic diagnostics from the program for the next affected file and caches it * Returns undefined if the iteration is complete @@ -502,7 +502,7 @@ namespace ts { * The builder that can handle the changes in program and iterate through changed file to emit the files * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files */ - export interface EmitAndSemanticDiagnosticsBuilderProgram extends BaseBuilderProgram { + export interface EmitAndSemanticDiagnosticsBuilderProgram extends BuilderProgram { /** * Get the current directory of the program */ diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 0b197c6a0f6..abf29d59c8b 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -894,12 +894,12 @@ namespace ts { /** * Creates the watch from the host for root files and compiler options */ - export function createBuilderWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfFilesAndCompilerOptions; + export function createWatchBuilderProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfFilesAndCompilerOptions; /** * Creates the watch from the host for config file */ - export function createBuilderWatchProgram(host: WatchCompilerHostOfConfigFile & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfConfigFile; - export function createBuilderWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & WatchCompilerHostOfConfigFile & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { + export function createWatchBuilderProgram(host: WatchCompilerHostOfConfigFile & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfConfigFile; + export function createWatchBuilderProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & WatchCompilerHostOfConfigFile & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfFilesAndCompilerOptions | WatchOfConfigFile { const watch = createWatchProgram(host); let builderProgram: T | undefined; return { diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 8014fe09702..4678e23d443 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3767,7 +3767,7 @@ declare namespace ts { /** * Builder to manage the program state changes */ - interface BaseBuilderProgram { + interface BuilderProgram { /** * Returns current program */ @@ -3825,7 +3825,7 @@ declare namespace ts { /** * The builder that caches the semantic diagnostics for the program and handles the changed files and affected files */ - interface SemanticDiagnosticsBuilderProgram extends BaseBuilderProgram { + interface SemanticDiagnosticsBuilderProgram extends BuilderProgram { /** * Gets the semantic diagnostics from the program for the next affected file and caches it * Returns undefined if the iteration is complete @@ -3836,7 +3836,7 @@ declare namespace ts { * The builder that can handle the changes in program and iterate through changed file to emit the files * The semantic diagnostics are cached per file and managed by clearing for the changed/affected files */ - interface EmitAndSemanticDiagnosticsBuilderProgram extends BaseBuilderProgram { + interface EmitAndSemanticDiagnosticsBuilderProgram extends BuilderProgram { /** * Get the current directory of the program */ @@ -3974,11 +3974,11 @@ declare namespace ts { /** * Creates the watch from the host for root files and compiler options */ - function createBuilderWatchProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfFilesAndCompilerOptions; + function createWatchBuilderProgram(host: WatchCompilerHostOfFilesAndCompilerOptions & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfFilesAndCompilerOptions; /** * Creates the watch from the host for config file */ - function createBuilderWatchProgram(host: WatchCompilerHostOfConfigFile & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfConfigFile; + function createWatchBuilderProgram(host: WatchCompilerHostOfConfigFile & BuilderProgramHost, createBuilderProgram: (newProgram: Program, host: BuilderProgramHost, oldProgram?: T) => T): WatchOfConfigFile; } declare namespace ts { function parseCommandLine(commandLine: ReadonlyArray, readFile?: (path: string) => string | undefined): ParsedCommandLine; From cb2636679b9339a65d8d09771f1244078ba6c9f9 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 8 Dec 2017 12:35:37 -0800 Subject: [PATCH 042/172] When user provided resolution is used, invalidate resolutions for all files In this case there is no way to tell if resolution has changed so resolution cache wont have answers --- src/compiler/resolutionCache.ts | 6 +++--- src/compiler/watch.ts | 10 ++++++++-- tests/baselines/reference/api/typescript.d.ts | 2 ++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 92eea4140da..d365ec21379 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -14,7 +14,7 @@ namespace ts { invalidateResolutionOfFile(filePath: Path): void; removeResolutionsOfFile(filePath: Path): void; - createHasInvalidatedResolution(): HasInvalidatedResolution; + createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution; startCachingPerDirectoryResolution(): void; finishCachingPerDirectoryResolution(): void; @@ -159,8 +159,8 @@ namespace ts { return collected; } - function createHasInvalidatedResolution(): HasInvalidatedResolution { - if (allFilesHaveInvalidatedResolution) { + function createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution { + if (allFilesHaveInvalidatedResolution || forceAllFilesAsInvalidated) { // Any file asked would have invalidated resolution filesWithInvalidatedResolutions = undefined; return returnTrue; diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index abf29d59c8b..762487dc0c7 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -302,6 +302,8 @@ namespace ts { /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ + resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; /** Used to watch changes in source files, missing files needed to update the program or config file */ watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; @@ -520,7 +522,10 @@ namespace ts { compilerHost.resolveModuleNames = host.resolveModuleNames ? ((moduleNames, containingFile, reusedNames) => host.resolveModuleNames(moduleNames, containingFile, reusedNames)) : ((moduleNames, containingFile, reusedNames) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames)); - compilerHost.resolveTypeReferenceDirectives = resolutionCache.resolveTypeReferenceDirectives.bind(resolutionCache); + compilerHost.resolveTypeReferenceDirectives = host.resolveTypeReferenceDirectives ? + ((typeDirectiveNames, containingFile) => host.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile)) : + ((typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile)); + const userProvidedResolution = !!host.resolveModuleNames || !!host.resolveTypeReferenceDirectives; reportWatchDiagnostic(Diagnostics.Starting_compilation_in_watch_mode); synchronizeProgram(); @@ -542,7 +547,8 @@ namespace ts { } } - const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(); + // All resolutions are invalid if user provided resolutions + const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(userProvidedResolution); if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames)) { return program; } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 52b7d4bd707..427218aa390 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3894,6 +3894,8 @@ declare namespace ts { trace?(s: string): void; /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ + resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; /** Used to watch changes in source files, missing files needed to update the program or config file */ watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ From fa758cc357e72c6705da956eb41858db51840bb3 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Tue, 9 Jan 2018 15:33:54 -0800 Subject: [PATCH 043/172] Tidy up code to make it harder to call incorrectly --- src/services/pathCompletions.ts | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/services/pathCompletions.ts b/src/services/pathCompletions.ts index e9c8b48a7c4..584a83ad95c 100644 --- a/src/services/pathCompletions.ts +++ b/src/services/pathCompletions.ts @@ -326,29 +326,28 @@ namespace ts.Completions.PathCompletions { if (typeRoots) { for (const root of typeRoots) { - getCompletionEntriesFromDirectories(host, root, span, result); + getCompletionEntriesFromDirectories(root); } } - } - if (host.getDirectories) { // Also get all @types typings installed in visible node_modules directories for (const packageJson of findPackageJsons(scriptPath, host)) { const typesDir = combinePaths(getDirectoryPath(packageJson), "node_modules/@types"); - getCompletionEntriesFromDirectories(host, typesDir, span, result); + getCompletionEntriesFromDirectories(typesDir); } } return result; - } - function getCompletionEntriesFromDirectories(host: LanguageServiceHost, directory: string, span: TextSpan, result: Push) { - if (host.getDirectories && tryDirectoryExists(host, directory)) { - const directories = tryGetDirectories(host, directory); - if (directories) { - for (let typeDirectory of directories) { - typeDirectory = normalizePath(typeDirectory); - result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName, span)); + function getCompletionEntriesFromDirectories(directory: string) { + Debug.assert(!!host.getDirectories); + if (tryDirectoryExists(host, directory)) { + const directories = tryGetDirectories(host, directory); + if (directories) { + for (let typeDirectory of directories) { + typeDirectory = normalizePath(typeDirectory); + result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName, span)); + } } } } From db09a593d3e4d50cf6a218192a417bc9b7ebc761 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Tue, 9 Jan 2018 15:40:32 -0800 Subject: [PATCH 044/172] Unmangle package names from typings during completion --- src/compiler/moduleNameResolver.ts | 11 ++++++++--- src/services/pathCompletions.ts | 7 +++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 1cc11acd32d..532d076504b 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -1097,13 +1097,18 @@ namespace ts { export function getPackageNameFromAtTypesDirectory(mangledName: string): string { const withoutAtTypePrefix = removePrefix(mangledName, "@types/"); if (withoutAtTypePrefix !== mangledName) { - return stringContains(withoutAtTypePrefix, mangledScopedPackageSeparator) ? - "@" + withoutAtTypePrefix.replace(mangledScopedPackageSeparator, ts.directorySeparator) : - withoutAtTypePrefix; + return getPackageNameFromAtTypesDirectoryWithoutPrefix(withoutAtTypePrefix); } return mangledName; } + /* @internal */ + export function getPackageNameFromAtTypesDirectoryWithoutPrefix(withoutAtTypePrefix: string): string { + return stringContains(withoutAtTypePrefix, mangledScopedPackageSeparator) ? + "@" + withoutAtTypePrefix.replace(mangledScopedPackageSeparator, ts.directorySeparator) : + withoutAtTypePrefix; + } + function tryFindNonRelativeModuleNameInCache(cache: PerModuleNameCache | undefined, moduleName: string, containingDirectory: string, traceEnabled: boolean, host: ModuleResolutionHost): SearchResult { const result = cache && cache.get(containingDirectory); if (result) { diff --git a/src/services/pathCompletions.ts b/src/services/pathCompletions.ts index 584a83ad95c..f6f6667bbec 100644 --- a/src/services/pathCompletions.ts +++ b/src/services/pathCompletions.ts @@ -313,7 +313,8 @@ namespace ts.Completions.PathCompletions { function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, span: TextSpan, result: CompletionEntry[] = []): CompletionEntry[] { // Check for typings specified in compiler options if (options.types) { - for (const moduleName of options.types) { + for (const typesName of options.types) { + const moduleName = getPackageNameFromAtTypesDirectoryWithoutPrefix(typesName); result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span)); } } @@ -346,7 +347,9 @@ namespace ts.Completions.PathCompletions { if (directories) { for (let typeDirectory of directories) { typeDirectory = normalizePath(typeDirectory); - result.push(createCompletionEntryForModule(getBaseFileName(typeDirectory), ScriptElementKind.externalModuleName, span)); + const directoryName = getBaseFileName(typeDirectory); + const moduleName = getPackageNameFromAtTypesDirectoryWithoutPrefix(directoryName); + result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span)); } } } From e48312df54be770f7c097fad1bca77dc407be260 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Tue, 9 Jan 2018 16:54:58 -0800 Subject: [PATCH 045/172] De-dup typing module completions --- src/services/pathCompletions.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/services/pathCompletions.ts b/src/services/pathCompletions.ts index f6f6667bbec..98b1737adf3 100644 --- a/src/services/pathCompletions.ts +++ b/src/services/pathCompletions.ts @@ -312,10 +312,11 @@ namespace ts.Completions.PathCompletions { function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, span: TextSpan, result: CompletionEntry[] = []): CompletionEntry[] { // Check for typings specified in compiler options + let seen = createMap(); if (options.types) { for (const typesName of options.types) { const moduleName = getPackageNameFromAtTypesDirectoryWithoutPrefix(typesName); - result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span)); + pushResult(moduleName); } } else if (host.getDirectories) { @@ -349,11 +350,18 @@ namespace ts.Completions.PathCompletions { typeDirectory = normalizePath(typeDirectory); const directoryName = getBaseFileName(typeDirectory); const moduleName = getPackageNameFromAtTypesDirectoryWithoutPrefix(directoryName); - result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span)); + pushResult(moduleName); } } } } + + function pushResult(moduleName: string) { + if (!seen.has(moduleName)) { + result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName, span)); + seen.set(moduleName, true); + } + } } function findPackageJsons(directory: string, host: LanguageServiceHost): string[] { From 4acdca5258f895bbca8ee091184131686cddf731 Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 10 Jan 2018 01:46:36 +0000 Subject: [PATCH 046/172] Enforce strictNullChecks for RHS of empty destructuring assignment When strictNullChecks is on, check the RHS of the following destructuring assignments for possible null or undefined: const {} = ... const [] = ... let {} = ... let [] = ... ({} = ...) --- src/compiler/checker.ts | 9 +- .../strictNullEmptyDestructuring.errors.txt | 60 +++++++++++++ .../reference/strictNullEmptyDestructuring.js | 39 +++++++++ .../strictNullEmptyDestructuring.symbols | 49 +++++++++++ .../strictNullEmptyDestructuring.types | 87 +++++++++++++++++++ .../compiler/strictNullEmptyDestructuring.ts | 25 ++++++ 6 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/strictNullEmptyDestructuring.errors.txt create mode 100644 tests/baselines/reference/strictNullEmptyDestructuring.js create mode 100644 tests/baselines/reference/strictNullEmptyDestructuring.symbols create mode 100644 tests/baselines/reference/strictNullEmptyDestructuring.types create mode 100644 tests/cases/compiler/strictNullEmptyDestructuring.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 16bfd7fa7e2..7a93ad42105 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18908,6 +18908,9 @@ namespace ts { target = (target).left; } if (target.kind === SyntaxKind.ObjectLiteralExpression) { + if (strictNullChecks && (target).properties.length === 0) { + return checkNonNullType(sourceType, target); + } return checkObjectLiteralAssignment(target, sourceType); } if (target.kind === SyntaxKind.ArrayLiteralExpression) { @@ -21833,7 +21836,11 @@ namespace ts { if (isBindingPattern(node.name)) { // Don't validate for-in initializer as it is already an error if (node.initializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) { - checkTypeAssignableTo(checkExpressionCached(node.initializer), getWidenedTypeForVariableLikeDeclaration(node), node, /*headMessage*/ undefined); + const initializerType = checkExpressionCached(node.initializer); + if (strictNullChecks && node.name.elements.length === 0) { + checkNonNullType(initializerType, node); + } + checkTypeAssignableTo(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, /*headMessage*/ undefined); checkParameterInitializer(node); } return; diff --git a/tests/baselines/reference/strictNullEmptyDestructuring.errors.txt b/tests/baselines/reference/strictNullEmptyDestructuring.errors.txt new file mode 100644 index 00000000000..2406ac27d98 --- /dev/null +++ b/tests/baselines/reference/strictNullEmptyDestructuring.errors.txt @@ -0,0 +1,60 @@ +tests/cases/compiler/strictNullEmptyDestructuring.ts(3,5): error TS2531: Object is possibly 'null'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(5,5): error TS2531: Object is possibly 'null'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(7,2): error TS2531: Object is possibly 'null'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(9,5): error TS2532: Object is possibly 'undefined'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(11,2): error TS2532: Object is possibly 'undefined'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(13,5): error TS2531: Object is possibly 'null'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(15,2): error TS2531: Object is possibly 'null'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(17,5): error TS2532: Object is possibly 'undefined'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(19,2): error TS2532: Object is possibly 'undefined'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(21,5): error TS2533: Object is possibly 'null' or 'undefined'. +tests/cases/compiler/strictNullEmptyDestructuring.ts(23,2): error TS2533: Object is possibly 'null' or 'undefined'. + + +==== tests/cases/compiler/strictNullEmptyDestructuring.ts (11 errors) ==== + // Repro from #20873 + + let [] = null; + ~~ +!!! error TS2531: Object is possibly 'null'. + + let { } = null; + ~~~ +!!! error TS2531: Object is possibly 'null'. + + ({} = null); + ~~ +!!! error TS2531: Object is possibly 'null'. + + let { } = undefined; + ~~~ +!!! error TS2532: Object is possibly 'undefined'. + + ({} = undefined); + ~~ +!!! error TS2532: Object is possibly 'undefined'. + + let { } = Math.random() ? {} : null; + ~~~ +!!! error TS2531: Object is possibly 'null'. + + ({} = Math.random() ? {} : null); + ~~ +!!! error TS2531: Object is possibly 'null'. + + let { } = Math.random() ? {} : undefined; + ~~~ +!!! error TS2532: Object is possibly 'undefined'. + + ({} = Math.random() ? {} : undefined); + ~~ +!!! error TS2532: Object is possibly 'undefined'. + + let { } = Math.random() ? null : undefined; + ~~~ +!!! error TS2533: Object is possibly 'null' or 'undefined'. + + ({} = Math.random() ? null : undefined); + ~~ +!!! error TS2533: Object is possibly 'null' or 'undefined'. + \ No newline at end of file diff --git a/tests/baselines/reference/strictNullEmptyDestructuring.js b/tests/baselines/reference/strictNullEmptyDestructuring.js new file mode 100644 index 00000000000..c46b44e21d3 --- /dev/null +++ b/tests/baselines/reference/strictNullEmptyDestructuring.js @@ -0,0 +1,39 @@ +//// [strictNullEmptyDestructuring.ts] +// Repro from #20873 + +let [] = null; + +let { } = null; + +({} = null); + +let { } = undefined; + +({} = undefined); + +let { } = Math.random() ? {} : null; + +({} = Math.random() ? {} : null); + +let { } = Math.random() ? {} : undefined; + +({} = Math.random() ? {} : undefined); + +let { } = Math.random() ? null : undefined; + +({} = Math.random() ? null : undefined); + + +//// [strictNullEmptyDestructuring.js] +// Repro from #20873 +var _a = null; +var _b = null; +(null); +var _c = undefined; +(undefined); +var _d = Math.random() ? {} : null; +(Math.random() ? {} : null); +var _e = Math.random() ? {} : undefined; +(Math.random() ? {} : undefined); +var _f = Math.random() ? null : undefined; +(Math.random() ? null : undefined); diff --git a/tests/baselines/reference/strictNullEmptyDestructuring.symbols b/tests/baselines/reference/strictNullEmptyDestructuring.symbols new file mode 100644 index 00000000000..5968efa4925 --- /dev/null +++ b/tests/baselines/reference/strictNullEmptyDestructuring.symbols @@ -0,0 +1,49 @@ +=== tests/cases/compiler/strictNullEmptyDestructuring.ts === +// Repro from #20873 + +let [] = null; + +let { } = null; + +({} = null); + +let { } = undefined; +>undefined : Symbol(undefined) + +({} = undefined); +>undefined : Symbol(undefined) + +let { } = Math.random() ? {} : null; +>Math.random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.d.ts, --, --)) + +({} = Math.random() ? {} : null); +>Math.random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.d.ts, --, --)) + +let { } = Math.random() ? {} : undefined; +>Math.random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>undefined : Symbol(undefined) + +({} = Math.random() ? {} : undefined); +>Math.random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>undefined : Symbol(undefined) + +let { } = Math.random() ? null : undefined; +>Math.random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>undefined : Symbol(undefined) + +({} = Math.random() ? null : undefined); +>Math.random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.d.ts, --, --)) +>undefined : Symbol(undefined) + diff --git a/tests/baselines/reference/strictNullEmptyDestructuring.types b/tests/baselines/reference/strictNullEmptyDestructuring.types new file mode 100644 index 00000000000..10215a0e0d1 --- /dev/null +++ b/tests/baselines/reference/strictNullEmptyDestructuring.types @@ -0,0 +1,87 @@ +=== tests/cases/compiler/strictNullEmptyDestructuring.ts === +// Repro from #20873 + +let [] = null; +>null : null + +let { } = null; +>null : null + +({} = null); +>({} = null) : any +>{} = null : any +>{} : {} +>null : null + +let { } = undefined; +>undefined : undefined + +({} = undefined); +>({} = undefined) : any +>{} = undefined : any +>{} : {} +>undefined : undefined + +let { } = Math.random() ? {} : null; +>Math.random() ? {} : null : {} | null +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>{} : {} +>null : null + +({} = Math.random() ? {} : null); +>({} = Math.random() ? {} : null) : {} +>{} = Math.random() ? {} : null : {} +>{} : {} +>Math.random() ? {} : null : {} | null +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>{} : {} +>null : null + +let { } = Math.random() ? {} : undefined; +>Math.random() ? {} : undefined : {} | undefined +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>{} : {} +>undefined : undefined + +({} = Math.random() ? {} : undefined); +>({} = Math.random() ? {} : undefined) : {} +>{} = Math.random() ? {} : undefined : {} +>{} : {} +>Math.random() ? {} : undefined : {} | undefined +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>{} : {} +>undefined : undefined + +let { } = Math.random() ? null : undefined; +>Math.random() ? null : undefined : null | undefined +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>null : null +>undefined : undefined + +({} = Math.random() ? null : undefined); +>({} = Math.random() ? null : undefined) : any +>{} = Math.random() ? null : undefined : any +>{} : {} +>Math.random() ? null : undefined : null | undefined +>Math.random() : number +>Math.random : () => number +>Math : Math +>random : () => number +>null : null +>undefined : undefined + diff --git a/tests/cases/compiler/strictNullEmptyDestructuring.ts b/tests/cases/compiler/strictNullEmptyDestructuring.ts new file mode 100644 index 00000000000..97126154182 --- /dev/null +++ b/tests/cases/compiler/strictNullEmptyDestructuring.ts @@ -0,0 +1,25 @@ +// @strictNullChecks: true + +// Repro from #20873 + +let [] = null; + +let { } = null; + +({} = null); + +let { } = undefined; + +({} = undefined); + +let { } = Math.random() ? {} : null; + +({} = Math.random() ? {} : null); + +let { } = Math.random() ? {} : undefined; + +({} = Math.random() ? {} : undefined); + +let { } = Math.random() ? null : undefined; + +({} = Math.random() ? null : undefined); From b9543bf6172b2249c18dd0db0cbde7ae0188c22c Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 10 Jan 2018 15:20:04 +0000 Subject: [PATCH 047/172] Update initializerType when checking RHS of empty object destructure --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7a93ad42105..4cb639684c3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21836,9 +21836,9 @@ namespace ts { if (isBindingPattern(node.name)) { // Don't validate for-in initializer as it is already an error if (node.initializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) { - const initializerType = checkExpressionCached(node.initializer); + let initializerType = checkExpressionCached(node.initializer); if (strictNullChecks && node.name.elements.length === 0) { - checkNonNullType(initializerType, node); + initializerType = checkNonNullType(initializerType, node); } checkTypeAssignableTo(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, /*headMessage*/ undefined); checkParameterInitializer(node); From 4a86bc60a3150113f772379ee435e2ca46bdb7ac Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 10 Jan 2018 19:48:48 +0000 Subject: [PATCH 048/172] Add review suggestions Move object destructuring assignment to checkObjectLiteralAssignment Only check assignability of types in checkVariableLikeDeclaration for object/array destructuring when there are properties present in the pattern. --- src/compiler/checker.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4cb639684c3..6cb777651a6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18769,6 +18769,9 @@ namespace ts { function checkObjectLiteralAssignment(node: ObjectLiteralExpression, sourceType: Type): Type { const properties = node.properties; + if (strictNullChecks && properties.length === 0) { + return checkNonNullType(sourceType, node); + } for (const p of properties) { checkObjectLiteralDestructuringPropertyAssignment(sourceType, p, properties); } @@ -18908,9 +18911,6 @@ namespace ts { target = (target).left; } if (target.kind === SyntaxKind.ObjectLiteralExpression) { - if (strictNullChecks && (target).properties.length === 0) { - return checkNonNullType(sourceType, target); - } return checkObjectLiteralAssignment(target, sourceType); } if (target.kind === SyntaxKind.ArrayLiteralExpression) { @@ -21836,12 +21836,14 @@ namespace ts { if (isBindingPattern(node.name)) { // Don't validate for-in initializer as it is already an error if (node.initializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) { - let initializerType = checkExpressionCached(node.initializer); + const initializerType = checkExpressionCached(node.initializer); if (strictNullChecks && node.name.elements.length === 0) { - initializerType = checkNonNullType(initializerType, node); + checkNonNullType(initializerType, node); + } + else { + checkTypeAssignableTo(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, /*headMessage*/ undefined); + checkParameterInitializer(node); } - checkTypeAssignableTo(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, /*headMessage*/ undefined); - checkParameterInitializer(node); } return; } From b16594b2393cbf7ff2d03e7ed343b6d29447e1de Mon Sep 17 00:00:00 2001 From: Jack Williams Date: Wed, 10 Jan 2018 20:33:00 +0000 Subject: [PATCH 049/172] Ensure checkParameterInitializer always gets called --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6cb777651a6..12f7833ad61 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21842,8 +21842,8 @@ namespace ts { } else { checkTypeAssignableTo(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, /*headMessage*/ undefined); - checkParameterInitializer(node); } + checkParameterInitializer(node); } return; } From 71c92bf2f6ec864e3b794c3b919915fe2c15116c Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 10 Jan 2018 13:38:25 -0800 Subject: [PATCH 050/172] Fix lint error --- src/services/pathCompletions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/pathCompletions.ts b/src/services/pathCompletions.ts index 98b1737adf3..9ed612f0ef3 100644 --- a/src/services/pathCompletions.ts +++ b/src/services/pathCompletions.ts @@ -312,7 +312,7 @@ namespace ts.Completions.PathCompletions { function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, span: TextSpan, result: CompletionEntry[] = []): CompletionEntry[] { // Check for typings specified in compiler options - let seen = createMap(); + const seen = createMap(); if (options.types) { for (const typesName of options.types) { const moduleName = getPackageNameFromAtTypesDirectoryWithoutPrefix(typesName); From 5d8598f2808b56bfce526defd45ea8fa0d9ec0cb Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 10 Jan 2018 13:38:32 -0800 Subject: [PATCH 051/172] Add regression test --- .../fourslash/completionListInImportClause05.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/cases/fourslash/completionListInImportClause05.ts diff --git a/tests/cases/fourslash/completionListInImportClause05.ts b/tests/cases/fourslash/completionListInImportClause05.ts new file mode 100644 index 00000000000..b98e91c4cbb --- /dev/null +++ b/tests/cases/fourslash/completionListInImportClause05.ts @@ -0,0 +1,17 @@ +/// + +// @Filename: app.ts +//// import * as A from "[|/*1*/|]"; + +// @Filename: /node_modules/@types/a__b/index.d.ts +////declare module "@e/f" { function fun(): string; } + +// @Filename: /node_modules/@types/c__d/index.d.ts +////export declare let x: number; + +const [replacementSpan] = test.ranges(); +verify.completionsAt("1", [ + { name: "@a/b", replacementSpan }, + { name: "@c/d", replacementSpan }, + { name: "@e/f", replacementSpan }, +]); From 8275bed33fd75dcc2f1c7ebcd1e7ba08b3cbb9f5 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 10 Jan 2018 13:57:06 -0800 Subject: [PATCH 052/172] Add comment explaining test-only workaround --- tests/cases/fourslash/completionListInImportClause05.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/cases/fourslash/completionListInImportClause05.ts b/tests/cases/fourslash/completionListInImportClause05.ts index b98e91c4cbb..e1401706ea1 100644 --- a/tests/cases/fourslash/completionListInImportClause05.ts +++ b/tests/cases/fourslash/completionListInImportClause05.ts @@ -9,6 +9,11 @@ // @Filename: /node_modules/@types/c__d/index.d.ts ////export declare let x: number; +// NOTE: When performing completion, the "current directory" appears to be "/", +// which is well above "." (i.e. the directory containing "app.ts"). This issue +// is specific to the virtual file system, so just work around it by putting the +// node modules folder in "/", rather than ".". + const [replacementSpan] = test.ranges(); verify.completionsAt("1", [ { name: "@a/b", replacementSpan }, From 0b23811a5635e41c8d4cf3bd156930617019760c Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 10 Jan 2018 14:00:52 -0800 Subject: [PATCH 053/172] Handle indexed mapped types in transformIndexedAccessType Also rename transformIndexedAccessType to simplifyIndexedAccessType --- src/compiler/checker.ts | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d530ef8e334..a5e21ff6535 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6351,7 +6351,7 @@ namespace ts { return getObjectFlags(type) & ObjectFlags.Mapped && !!(type).declaration.questionToken; } - function isGenericMappedType(type: Type) { + function isGenericMappedType(type: Type): type is MappedType { return getObjectFlags(type) & ObjectFlags.Mapped && isGenericIndexType(getConstraintTypeFromMappedType(type)); } @@ -6463,14 +6463,12 @@ namespace ts { } function getConstraintOfIndexedAccess(type: IndexedAccessType) { - const transformed = getTransformedIndexedAccessType(type); + const transformed = simplifyIndexedAccessType(type); if (transformed) { return transformed; } const baseObjectType = getBaseConstraintOfType(type.objectType); - const keepTypeParameterForMappedType = baseObjectType && getObjectFlags(baseObjectType) & ObjectFlags.Mapped && - type.indexType.flags & TypeFlags.TypeParameter; - const baseIndexType = !keepTypeParameterForMappedType && getBaseConstraintOfType(type.indexType); + const baseIndexType = getBaseConstraintOfType(type.indexType); if (baseIndexType === stringType && !getIndexInfoOfType(baseObjectType || type.objectType, IndexKind.String)) { // getIndexedAccessType returns `any` for X[string] where X doesn't have an index signature. // to avoid this, return `undefined`. @@ -6546,7 +6544,7 @@ namespace ts { return stringType; } if (t.flags & TypeFlags.IndexedAccess) { - const transformed = getTransformedIndexedAccessType(t); + const transformed = simplifyIndexedAccessType(t); if (transformed) { return getBaseConstraint(transformed); } @@ -6555,6 +6553,9 @@ namespace ts { const baseIndexedAccess = baseObjectType && baseIndexType ? getIndexedAccessType(baseObjectType, baseIndexType) : undefined; return baseIndexedAccess && baseIndexedAccess !== unknownType ? getBaseConstraint(baseIndexedAccess) : undefined; } + if (isGenericMappedType(t)) { + return emptyObjectType; + } return t; } } @@ -8355,7 +8356,7 @@ namespace ts { // Transform an indexed access to a simpler form, if possible. Return the simpler form, or return // undefined if no transformation is possible. - function getTransformedIndexedAccessType(type: IndexedAccessType): Type { + function simplifyIndexedAccessType(type: IndexedAccessType): Type { const objectType = type.objectType; // Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or // more object types with only a string index signature, e.g. '(U & V & { [x: string]: D })[K]', return a @@ -8381,14 +8382,24 @@ namespace ts { // that substitutes the index type for P. For example, for an index access { [P in K]: Box }[X], we // construct the type Box. if (isGenericMappedType(objectType)) { - const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [type.indexType]); - const objectTypeMapper = (objectType).mapper; - const templateMapper = objectTypeMapper ? combineTypeMappers(objectTypeMapper, mapper) : mapper; - return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper); + return substituteIndexedMappedType(objectType, type); + } + if (objectType.flags & TypeFlags.TypeParameter) { + const constraint = getConstraintFromTypeParameter(objectType as TypeParameter); + if (constraint && isGenericMappedType(constraint)) { + return substituteIndexedMappedType(constraint, type); + } } return undefined; } + function substituteIndexedMappedType(objectType: MappedType, type: IndexedAccessType) { + const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [type.indexType]); + const objectTypeMapper = (objectType).mapper; + const templateMapper = objectTypeMapper ? combineTypeMappers(objectTypeMapper, mapper) : mapper; + return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper); + } + function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode): Type { // If the index type is generic, or if the object type is generic and doesn't originate in an expression, // we are performing a higher-order index access where we cannot meaningfully access the properties of the From 8f45373e442034fb94aef20f9f7b65dc8d38ebb9 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 10 Jan 2018 14:43:55 -0800 Subject: [PATCH 054/172] Rename simplifyIndexedAccessType->getSimplifiedIndexedAccessType --- src/compiler/checker.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a5e21ff6535..8504074080a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6463,7 +6463,7 @@ namespace ts { } function getConstraintOfIndexedAccess(type: IndexedAccessType) { - const transformed = simplifyIndexedAccessType(type); + const transformed = getSimplifiedIndexedAccessType(type); if (transformed) { return transformed; } @@ -6544,7 +6544,7 @@ namespace ts { return stringType; } if (t.flags & TypeFlags.IndexedAccess) { - const transformed = simplifyIndexedAccessType(t); + const transformed = getSimplifiedIndexedAccessType(t); if (transformed) { return getBaseConstraint(transformed); } @@ -8356,7 +8356,7 @@ namespace ts { // Transform an indexed access to a simpler form, if possible. Return the simpler form, or return // undefined if no transformation is possible. - function simplifyIndexedAccessType(type: IndexedAccessType): Type { + function getSimplifiedIndexedAccessType(type: IndexedAccessType): Type { const objectType = type.objectType; // Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or // more object types with only a string index signature, e.g. '(U & V & { [x: string]: D })[K]', return a From ff102da9d1a77bd148d7c713ad109e65edfcba65 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 10 Jan 2018 15:12:00 -0800 Subject: [PATCH 055/172] Refine explanatory comment --- tests/cases/fourslash/completionListInImportClause05.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/cases/fourslash/completionListInImportClause05.ts b/tests/cases/fourslash/completionListInImportClause05.ts index e1401706ea1..f7b5244d5a4 100644 --- a/tests/cases/fourslash/completionListInImportClause05.ts +++ b/tests/cases/fourslash/completionListInImportClause05.ts @@ -1,7 +1,7 @@ /// // @Filename: app.ts -//// import * as A from "[|/*1*/|]"; +////import * as A from "[|/*1*/|]"; // @Filename: /node_modules/@types/a__b/index.d.ts ////declare module "@e/f" { function fun(): string; } @@ -9,10 +9,8 @@ // @Filename: /node_modules/@types/c__d/index.d.ts ////export declare let x: number; -// NOTE: When performing completion, the "current directory" appears to be "/", -// which is well above "." (i.e. the directory containing "app.ts"). This issue -// is specific to the virtual file system, so just work around it by putting the -// node modules folder in "/", rather than ".". +// NOTE: The node_modules folder is in "/", rather than ".", because it requires +// less scaffolding to mock. In particular, "/" is where we look for type roots. const [replacementSpan] = test.ranges(); verify.completionsAt("1", [ From 211be0ae69f217db437ca13185ad76586aab5054 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 10 Jan 2018 15:12:10 -0800 Subject: [PATCH 056/172] Add regression test for de-dup'ing. --- .../fourslash/completionListInImportClause06.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/cases/fourslash/completionListInImportClause06.ts diff --git a/tests/cases/fourslash/completionListInImportClause06.ts b/tests/cases/fourslash/completionListInImportClause06.ts new file mode 100644 index 00000000000..f6cfefa437f --- /dev/null +++ b/tests/cases/fourslash/completionListInImportClause06.ts @@ -0,0 +1,17 @@ +/// + +// @typeRoots: T1,T2 + +// @Filename: app.ts +////import * as A from "[|/*1*/|]"; + +// @Filename: T1/a__b/index.d.ts +////export declare let x: number; + +// @Filename: T2/a__b/index.d.ts +////export declare let x: number; + +// Confirm that entries are de-dup'd. +verify.completionsAt("1", [ + { name: "@a/b", replacementSpan: test.ranges()[0] }, +]); From 9a4fe8eb7e3242cbca2aa430eb7200e0ad6d0001 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 10 Jan 2018 15:17:27 -0800 Subject: [PATCH 057/172] Rename getPackageNameFromAtTypesDirectoryWithoutPrefix to getUnmangledNameForScopedPackage --- src/compiler/moduleNameResolver.ts | 10 +++++----- src/services/pathCompletions.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 532d076504b..018bddd420b 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -1097,16 +1097,16 @@ namespace ts { export function getPackageNameFromAtTypesDirectory(mangledName: string): string { const withoutAtTypePrefix = removePrefix(mangledName, "@types/"); if (withoutAtTypePrefix !== mangledName) { - return getPackageNameFromAtTypesDirectoryWithoutPrefix(withoutAtTypePrefix); + return getUnmangledNameForScopedPackage(withoutAtTypePrefix); } return mangledName; } /* @internal */ - export function getPackageNameFromAtTypesDirectoryWithoutPrefix(withoutAtTypePrefix: string): string { - return stringContains(withoutAtTypePrefix, mangledScopedPackageSeparator) ? - "@" + withoutAtTypePrefix.replace(mangledScopedPackageSeparator, ts.directorySeparator) : - withoutAtTypePrefix; + export function getUnmangledNameForScopedPackage(typesPackageName: string): string { + return stringContains(typesPackageName, mangledScopedPackageSeparator) ? + "@" + typesPackageName.replace(mangledScopedPackageSeparator, ts.directorySeparator) : + typesPackageName; } function tryFindNonRelativeModuleNameInCache(cache: PerModuleNameCache | undefined, moduleName: string, containingDirectory: string, traceEnabled: boolean, host: ModuleResolutionHost): SearchResult { diff --git a/src/services/pathCompletions.ts b/src/services/pathCompletions.ts index 9ed612f0ef3..82f144ac377 100644 --- a/src/services/pathCompletions.ts +++ b/src/services/pathCompletions.ts @@ -315,7 +315,7 @@ namespace ts.Completions.PathCompletions { const seen = createMap(); if (options.types) { for (const typesName of options.types) { - const moduleName = getPackageNameFromAtTypesDirectoryWithoutPrefix(typesName); + const moduleName = getUnmangledNameForScopedPackage(typesName); pushResult(moduleName); } } @@ -349,7 +349,7 @@ namespace ts.Completions.PathCompletions { for (let typeDirectory of directories) { typeDirectory = normalizePath(typeDirectory); const directoryName = getBaseFileName(typeDirectory); - const moduleName = getPackageNameFromAtTypesDirectoryWithoutPrefix(directoryName); + const moduleName = getUnmangledNameForScopedPackage(directoryName); pushResult(moduleName); } } From f0ef9a08d86ae92815559e66c8ceb63a6c953830 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 10 Jan 2018 15:37:27 -0800 Subject: [PATCH 058/172] getConstraintOfTypeParameter:check circularity in base constraint --- src/compiler/checker.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fd6a033653a..f67f90a3d04 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6301,9 +6301,9 @@ namespace ts { (type.typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(type.declaration.typeParameter))); } - function getConstraintTypeFromMappedType(type: MappedType) { + function getConstraintTypeFromMappedType(type: MappedType, typeStack?: Type[]) { return type.constraintType || - (type.constraintType = instantiateType(getConstraintOfTypeParameter(getTypeParameterFromMappedType(type)), type.mapper || identityMapper) || unknownType); + (type.constraintType = instantiateType(getConstraintOfTypeParameter(getTypeParameterFromMappedType(type), typeStack), type.mapper || identityMapper) || unknownType); } function getTemplateTypeFromMappedType(type: MappedType) { @@ -6351,8 +6351,8 @@ namespace ts { return getObjectFlags(type) & ObjectFlags.Mapped && !!(type).declaration.questionToken; } - function isGenericMappedType(type: Type) { - return getObjectFlags(type) & ObjectFlags.Mapped && isGenericIndexType(getConstraintTypeFromMappedType(type)); + function isGenericMappedType(type: Type, typeStack?: Type[]): type is MappedType { + return getObjectFlags(type) & ObjectFlags.Mapped && isGenericIndexType(getConstraintTypeFromMappedType(type, typeStack)); } function resolveStructuredTypeMembers(type: StructuredType): ResolvedType { @@ -6458,7 +6458,10 @@ namespace ts { getBaseConstraintOfType(type); } - function getConstraintOfTypeParameter(typeParameter: TypeParameter): Type { + function getConstraintOfTypeParameter(typeParameter: TypeParameter, typeStack?: Type[]): Type { + if (typeStack) { + return !contains(typeStack, typeParameter) && getConstraintFromTypeParameter(typeParameter); + } return hasNonCircularBaseConstraint(typeParameter) ? getConstraintFromTypeParameter(typeParameter) : undefined; } @@ -6547,7 +6550,7 @@ namespace ts { const baseIndexedAccess = baseObjectType && baseIndexType ? getIndexedAccessType(baseObjectType, baseIndexType) : undefined; return baseIndexedAccess && baseIndexedAccess !== unknownType ? getBaseConstraint(baseIndexedAccess) : undefined; } - if (isGenericMappedType(t)) { + if (isGenericMappedType(t, typeStack)) { return emptyObjectType; } return t; From c0dd832e468aa60f18a538cd2d9fe8d8e7ec4bc7 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 10 Jan 2018 15:38:22 -0800 Subject: [PATCH 059/172] Test:incorrect mapped type doesn't infinitely recur --- ...ctRecursiveMappedTypeConstraint.errors.txt | 13 +++++++++++ .../incorrectRecursiveMappedTypeConstraint.js | 12 ++++++++++ ...rrectRecursiveMappedTypeConstraint.symbols | 21 +++++++++++++++++ ...correctRecursiveMappedTypeConstraint.types | 23 +++++++++++++++++++ .../incorrectRecursiveMappedTypeConstraint.ts | 4 ++++ .../TypeScript-Node-Starter | 2 +- 6 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.errors.txt create mode 100644 tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.js create mode 100644 tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.symbols create mode 100644 tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.types create mode 100644 tests/cases/compiler/incorrectRecursiveMappedTypeConstraint.ts diff --git a/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.errors.txt b/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.errors.txt new file mode 100644 index 00000000000..8773f92549f --- /dev/null +++ b/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.errors.txt @@ -0,0 +1,13 @@ +tests/cases/compiler/incorrectRecursiveMappedTypeConstraint.ts(2,32): error TS2322: Type 'T' is not assignable to type 'string'. + Type '{ [P in T]: number; }' is not assignable to type 'string'. + + +==== tests/cases/compiler/incorrectRecursiveMappedTypeConstraint.ts (1 errors) ==== + // #17847 + function sum(n: number, v: T, k: K) { + ~ +!!! error TS2322: Type 'T' is not assignable to type 'string'. +!!! error TS2322: Type '{ [P in T]: number; }' is not assignable to type 'string'. + n += v[k]; + } + \ No newline at end of file diff --git a/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.js b/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.js new file mode 100644 index 00000000000..44954f5c7c2 --- /dev/null +++ b/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.js @@ -0,0 +1,12 @@ +//// [incorrectRecursiveMappedTypeConstraint.ts] +// #17847 +function sum(n: number, v: T, k: K) { + n += v[k]; +} + + +//// [incorrectRecursiveMappedTypeConstraint.js] +// #17847 +function sum(n, v, k) { + n += v[k]; +} diff --git a/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.symbols b/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.symbols new file mode 100644 index 00000000000..42cc7b1f4fb --- /dev/null +++ b/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.symbols @@ -0,0 +1,21 @@ +=== tests/cases/compiler/incorrectRecursiveMappedTypeConstraint.ts === +// #17847 +function sum(n: number, v: T, k: K) { +>sum : Symbol(sum, Decl(incorrectRecursiveMappedTypeConstraint.ts, 0, 0)) +>T : Symbol(T, Decl(incorrectRecursiveMappedTypeConstraint.ts, 1, 13)) +>P : Symbol(P, Decl(incorrectRecursiveMappedTypeConstraint.ts, 1, 26)) +>T : Symbol(T, Decl(incorrectRecursiveMappedTypeConstraint.ts, 1, 13)) +>K : Symbol(K, Decl(incorrectRecursiveMappedTypeConstraint.ts, 1, 44)) +>T : Symbol(T, Decl(incorrectRecursiveMappedTypeConstraint.ts, 1, 13)) +>n : Symbol(n, Decl(incorrectRecursiveMappedTypeConstraint.ts, 1, 64)) +>v : Symbol(v, Decl(incorrectRecursiveMappedTypeConstraint.ts, 1, 74)) +>T : Symbol(T, Decl(incorrectRecursiveMappedTypeConstraint.ts, 1, 13)) +>k : Symbol(k, Decl(incorrectRecursiveMappedTypeConstraint.ts, 1, 80)) +>K : Symbol(K, Decl(incorrectRecursiveMappedTypeConstraint.ts, 1, 44)) + + n += v[k]; +>n : Symbol(n, Decl(incorrectRecursiveMappedTypeConstraint.ts, 1, 64)) +>v : Symbol(v, Decl(incorrectRecursiveMappedTypeConstraint.ts, 1, 74)) +>k : Symbol(k, Decl(incorrectRecursiveMappedTypeConstraint.ts, 1, 80)) +} + diff --git a/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.types b/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.types new file mode 100644 index 00000000000..9dee691eaf0 --- /dev/null +++ b/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.types @@ -0,0 +1,23 @@ +=== tests/cases/compiler/incorrectRecursiveMappedTypeConstraint.ts === +// #17847 +function sum(n: number, v: T, k: K) { +>sum : (n: number, v: T, k: K) => void +>T : T +>P : P +>T : T +>K : K +>T : T +>n : number +>v : T +>T : T +>k : K +>K : K + + n += v[k]; +>n += v[k] : number +>n : number +>v[k] : T[K] +>v : T +>k : K +} + diff --git a/tests/cases/compiler/incorrectRecursiveMappedTypeConstraint.ts b/tests/cases/compiler/incorrectRecursiveMappedTypeConstraint.ts new file mode 100644 index 00000000000..67e996c975b --- /dev/null +++ b/tests/cases/compiler/incorrectRecursiveMappedTypeConstraint.ts @@ -0,0 +1,4 @@ +// #17847 +function sum(n: number, v: T, k: K) { + n += v[k]; +} diff --git a/tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter b/tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter index 40bdb4eadab..ed149eb0c78 160000 --- a/tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter +++ b/tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter @@ -1 +1 @@ -Subproject commit 40bdb4eadabc9fbed7d83e3f26817a931c0763b6 +Subproject commit ed149eb0c787b1195a95b44105822c64bb6eb636 From e34a4101b5a17c861c0404d9e5383ac628814747 Mon Sep 17 00:00:00 2001 From: csigs Date: Thu, 11 Jan 2018 17:10:12 +0000 Subject: [PATCH 060/172] LEGO: check in for master to temporary branch. --- .../diagnosticMessages.generated.json.lcl | 118 +++++++++++++++++- 1 file changed, 116 insertions(+), 2 deletions(-) diff --git a/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl index b01830dcac5..093c228c5a4 100644 --- a/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -474,6 +474,15 @@ + + + + + + + + + @@ -876,6 +885,12 @@ + + + + + + @@ -1281,6 +1296,24 @@ + + + + + + + + + + + + + + + + + + @@ -1830,6 +1863,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2298,6 +2358,15 @@ + + + + + + + + + @@ -2895,6 +2964,15 @@ + + + + + + + + + @@ -4539,6 +4617,12 @@ + + + + + + @@ -5574,6 +5658,15 @@ + + + + + + + + + @@ -5931,6 +6024,15 @@ + + + + + + + + + @@ -7995,6 +8097,15 @@ + + + + + + + + + @@ -8498,10 +8609,13 @@ - + - + + + + From 16a80030117e00f4ab046db5a2d1ff009ddebccb Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 11 Jan 2018 10:07:59 -0800 Subject: [PATCH 061/172] push/popTypeResolution for circular base constraints Instead of a custom cache `typeStack`. --- src/compiler/checker.ts | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4b59cbc5d8a..d402f344d60 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -530,6 +530,7 @@ namespace ts { ResolvedBaseConstructorType, DeclaredType, ResolvedReturnType, + ResolvedBaseConstraint, } const enum CheckMode { @@ -4254,7 +4255,7 @@ namespace ts { return -1; } - function hasType(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): Type { + function hasType(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): Type | boolean { if (propertyName === TypeSystemPropertyName.Type) { return getSymbolLinks(target).type; } @@ -4267,6 +4268,10 @@ namespace ts { if (propertyName === TypeSystemPropertyName.ResolvedReturnType) { return (target).resolvedReturnType; } + if (propertyName === TypeSystemPropertyName.ResolvedBaseConstraint) { + const bc = (target).resolvedBaseConstraint; + return bc && bc !== circularConstraintType; + } Debug.fail("Unhandled TypeSystemPropertyName " + propertyName); } @@ -6301,9 +6306,9 @@ namespace ts { (type.typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(type.declaration.typeParameter))); } - function getConstraintTypeFromMappedType(type: MappedType, typeStack?: Type[]) { + function getConstraintTypeFromMappedType(type: MappedType) { return type.constraintType || - (type.constraintType = instantiateType(getConstraintOfTypeParameter(getTypeParameterFromMappedType(type), typeStack), type.mapper || identityMapper) || unknownType); + (type.constraintType = instantiateType(getConstraintOfTypeParameter(getTypeParameterFromMappedType(type)), type.mapper || identityMapper) || unknownType); } function getTemplateTypeFromMappedType(type: MappedType) { @@ -6351,8 +6356,8 @@ namespace ts { return getObjectFlags(type) & ObjectFlags.Mapped && !!(type).declaration.questionToken; } - function isGenericMappedType(type: Type, typeStack?: Type[]): type is MappedType { - return getObjectFlags(type) & ObjectFlags.Mapped && isGenericIndexType(getConstraintTypeFromMappedType(type, typeStack)); + function isGenericMappedType(type: Type): type is MappedType { + return getObjectFlags(type) & ObjectFlags.Mapped && isGenericIndexType(getConstraintTypeFromMappedType(type)); } function resolveStructuredTypeMembers(type: StructuredType): ResolvedType { @@ -6458,10 +6463,7 @@ namespace ts { getBaseConstraintOfType(type); } - function getConstraintOfTypeParameter(typeParameter: TypeParameter, typeStack?: Type[]): Type { - if (typeStack) { - return !contains(typeStack, typeParameter) && getConstraintFromTypeParameter(typeParameter); - } + function getConstraintOfTypeParameter(typeParameter: TypeParameter): Type { return hasNonCircularBaseConstraint(typeParameter) ? getConstraintFromTypeParameter(typeParameter) : undefined; } @@ -6503,23 +6505,23 @@ namespace ts { * circularly references the type variable. */ function getResolvedBaseConstraint(type: TypeVariable | UnionOrIntersectionType): Type { - let typeStack: Type[]; let circular: boolean; if (!type.resolvedBaseConstraint) { - typeStack = []; const constraint = getBaseConstraint(type); type.resolvedBaseConstraint = circular ? circularConstraintType : getTypeWithThisArgument(constraint || noConstraintType, type); } return type.resolvedBaseConstraint; function getBaseConstraint(t: Type): Type { - if (contains(typeStack, t)) { + if (!pushTypeResolution(t, TypeSystemPropertyName.ResolvedBaseConstraint)) { circular = true; return undefined; } - typeStack.push(t); const result = computeBaseConstraint(t); - typeStack.pop(); + if (!popTypeResolution()) { + circular = true; + return undefined; + } return result; } @@ -6556,7 +6558,7 @@ namespace ts { const baseIndexedAccess = baseObjectType && baseIndexType ? getIndexedAccessType(baseObjectType, baseIndexType) : undefined; return baseIndexedAccess && baseIndexedAccess !== unknownType ? getBaseConstraint(baseIndexedAccess) : undefined; } - if (isGenericMappedType(t, typeStack)) { + if (isGenericMappedType(t)) { return emptyObjectType; } return t; From 2b630e9ea5002b5a5bf7219d32e71e26e4e56a82 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 11 Jan 2018 10:08:49 -0800 Subject: [PATCH 062/172] Update baselines --- .../incorrectRecursiveMappedTypeConstraint.errors.txt | 6 ++---- .../incorrectRecursiveMappedTypeConstraint.types | 2 +- .../reference/recursiveMappedTypes.errors.txt | 11 ++++++++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.errors.txt b/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.errors.txt index 8773f92549f..5300c97a576 100644 --- a/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.errors.txt +++ b/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.errors.txt @@ -1,13 +1,11 @@ -tests/cases/compiler/incorrectRecursiveMappedTypeConstraint.ts(2,32): error TS2322: Type 'T' is not assignable to type 'string'. - Type '{ [P in T]: number; }' is not assignable to type 'string'. +tests/cases/compiler/incorrectRecursiveMappedTypeConstraint.ts(2,32): error TS2313: Type parameter 'P' has a circular constraint. ==== tests/cases/compiler/incorrectRecursiveMappedTypeConstraint.ts (1 errors) ==== // #17847 function sum(n: number, v: T, k: K) { ~ -!!! error TS2322: Type 'T' is not assignable to type 'string'. -!!! error TS2322: Type '{ [P in T]: number; }' is not assignable to type 'string'. +!!! error TS2313: Type parameter 'P' has a circular constraint. n += v[k]; } \ No newline at end of file diff --git a/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.types b/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.types index 9dee691eaf0..b6589956216 100644 --- a/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.types +++ b/tests/baselines/reference/incorrectRecursiveMappedTypeConstraint.types @@ -1,7 +1,7 @@ === tests/cases/compiler/incorrectRecursiveMappedTypeConstraint.ts === // #17847 function sum(n: number, v: T, k: K) { ->sum : (n: number, v: T, k: K) => void +>sum : (n: number, v: T, k: K) => void >T : T >P : P >T : T diff --git a/tests/baselines/reference/recursiveMappedTypes.errors.txt b/tests/baselines/reference/recursiveMappedTypes.errors.txt index 440825f5319..8279f4f60a6 100644 --- a/tests/baselines/reference/recursiveMappedTypes.errors.txt +++ b/tests/baselines/reference/recursiveMappedTypes.errors.txt @@ -1,25 +1,34 @@ tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(3,6): error TS2456: Type alias 'Recurse' circularly references itself. +tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(4,11): error TS2313: Type parameter 'K' has a circular constraint. tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(7,6): error TS2456: Type alias 'Recurse1' circularly references itself. +tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(8,11): error TS2313: Type parameter 'K' has a circular constraint. tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(11,6): error TS2456: Type alias 'Recurse2' circularly references itself. +tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(12,11): error TS2313: Type parameter 'K' has a circular constraint. -==== tests/cases/conformance/types/mapped/recursiveMappedTypes.ts (3 errors) ==== +==== tests/cases/conformance/types/mapped/recursiveMappedTypes.ts (6 errors) ==== // Recursive mapped types simply appear empty type Recurse = { ~~~~~~~ !!! error TS2456: Type alias 'Recurse' circularly references itself. [K in keyof Recurse]: Recurse[K] + ~~~~~~~~~~~~~ +!!! error TS2313: Type parameter 'K' has a circular constraint. } type Recurse1 = { ~~~~~~~~ !!! error TS2456: Type alias 'Recurse1' circularly references itself. [K in keyof Recurse2]: Recurse2[K] + ~~~~~~~~~~~~~~ +!!! error TS2313: Type parameter 'K' has a circular constraint. } type Recurse2 = { ~~~~~~~~ !!! error TS2456: Type alias 'Recurse2' circularly references itself. [K in keyof Recurse1]: Recurse1[K] + ~~~~~~~~~~~~~~ +!!! error TS2313: Type parameter 'K' has a circular constraint. } \ No newline at end of file From a77c6014b339e6310878821ee20b56fd7b3c41f1 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 11 Jan 2018 10:49:39 -0800 Subject: [PATCH 063/172] Parse comment on @property tag and use as documentation comment (#21119) * Parse comment on @property tag and use as documentation comment * Fix comment parsing bug -- back up after seeing `@` character * Add test for indent * Don't default comment to "" --- src/compiler/parser.ts | 46 +++++++++---------- src/compiler/utilities.ts | 17 ++----- src/services/jsDoc.ts | 36 ++++++++++----- ...ments.parsesCorrectly.leadingAsterisk.json | 3 +- ...nts.parsesCorrectly.noLeadingAsterisk.json | 3 +- ...Comments.parsesCorrectly.noReturnType.json | 3 +- ...cComments.parsesCorrectly.oneParamTag.json | 3 +- ...parsesCorrectly.paramTagNameThenType1.json | 3 +- ...ents.parsesCorrectly.paramWithoutType.json | 3 +- ...ocComments.parsesCorrectly.returnTag1.json | 3 +- ...cComments.parsesCorrectly.returnsTag1.json | 3 +- ...cComments.parsesCorrectly.templateTag.json | 3 +- ...Comments.parsesCorrectly.templateTag2.json | 3 +- ...Comments.parsesCorrectly.templateTag3.json | 3 +- ...Comments.parsesCorrectly.templateTag4.json | 3 +- ...Comments.parsesCorrectly.templateTag5.json | 3 +- ...Comments.parsesCorrectly.twoParamTag2.json | 6 +-- ...parsesCorrectly.twoParamTagOnSameLine.json | 6 +-- .../DocComments.parsesCorrectly.typeTag.json | 3 +- ...sCorrectly.typedefTagWithChildrenTags.json | 3 +- tests/baselines/reference/jsDocTags.baseline | 15 ++---- tests/baselines/reference/jsDocTypeTag1.js | 26 +++++------ tests/baselines/reference/jsDocTypeTag2.js | 24 +++++----- tests/baselines/reference/jsDocTypedef1.js | 2 +- .../reference/quickInfoJsDocTags.baseline | 4 +- tests/cases/fourslash/jsDocTags.ts | 2 +- tests/cases/fourslash/quickInfoPropertyTag.ts | 16 +++++++ 27 files changed, 122 insertions(+), 123 deletions(-) create mode 100644 tests/cases/fourslash/quickInfoPropertyTag.ts diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 2797fe557c7..e3677014996 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -6260,7 +6260,6 @@ namespace ts { scanner.scanRange(start + 3, length - 5, () => { // Initially we can parse out a tag. We also have seen a starting asterisk. // This is so that /** * @type */ doesn't parse. - let advanceToken = true; let state = JSDocState.SawAsterisk; let margin: number | undefined = undefined; // + 4 for leading '/** ' @@ -6292,7 +6291,6 @@ namespace ts { // Real-world comments may break this rule, so "BeginningOfLine" will not be a real line beginning // for malformed examples like `/** @param {string} x @returns {number} the length */` state = JSDocState.BeginningOfLine; - advanceToken = false; margin = undefined; indent++; } @@ -6344,13 +6342,7 @@ namespace ts { pushComment(scanner.getTokenText()); break; } - if (advanceToken) { - t = nextJSDocToken(); - } - else { - advanceToken = true; - t = currentToken as JsDocSyntaxKind; - } + t = nextJSDocToken(); } removeLeadingNewlines(comments); removeTrailingNewlines(comments); @@ -6446,10 +6438,11 @@ namespace ts { // a badly malformed tag should not be added to the list of tags return; } - addTag(tag, parseTagComments(indent + tag.end - tag.pos)); + tag.comment = parseTagComments(indent + tag.end - tag.pos); + addTag(tag); } - function parseTagComments(indent: number) { + function parseTagComments(indent: number): string | undefined { const comments: string[] = []; let state = JSDocState.BeginningOfLine; let margin: number | undefined; @@ -6471,6 +6464,8 @@ namespace ts { indent = 0; break; case SyntaxKind.AtToken: + scanner.setTextPos(scanner.getTextPos() - 1); + // falls through case SyntaxKind.EndOfFileToken: // Done break loop; @@ -6506,7 +6501,7 @@ namespace ts { removeLeadingNewlines(comments); removeTrailingNewlines(comments); - return comments; + return comments.length === 0 ? undefined : comments.join(""); } function parseUnknownTag(atToken: AtToken, tagName: Identifier) { @@ -6516,9 +6511,7 @@ namespace ts { return finishNode(result); } - function addTag(tag: JSDocTag, comments: string[]): void { - tag.comment = comments.join(""); - + function addTag(tag: JSDocTag): void { if (!tags) { tags = [tag]; tagsPos = tag.pos; @@ -6563,9 +6556,7 @@ namespace ts { } } - function parseParameterOrPropertyTag(atToken: AtToken, tagName: Identifier, target: PropertyLikeParse.Parameter): JSDocParameterTag; - function parseParameterOrPropertyTag(atToken: AtToken, tagName: Identifier, target: PropertyLikeParse.Property): JSDocPropertyTag; - function parseParameterOrPropertyTag(atToken: AtToken, tagName: Identifier, target: PropertyLikeParse): JSDocPropertyLikeTag { + function parseParameterOrPropertyTag(atToken: AtToken, tagName: Identifier, target: PropertyLikeParse): JSDocParameterTag | JSDocPropertyTag { let typeExpression = tryParseTypeExpression(); let isNameFirst = !typeExpression; skipWhitespace(); @@ -6577,7 +6568,7 @@ namespace ts { typeExpression = tryParseTypeExpression(); } - const result: JSDocPropertyLikeTag = target === PropertyLikeParse.Parameter ? + const result = target === PropertyLikeParse.Parameter ? createNode(SyntaxKind.JSDocParameterTag, atToken.pos) : createNode(SyntaxKind.JSDocPropertyTag, atToken.pos); const nestedTypeLiteral = parseNestedTypeLiteral(typeExpression, name); @@ -6592,7 +6583,6 @@ namespace ts { result.isNameFirst = isNameFirst; result.isBracketed = isBracketed; return finishNode(result); - } function parseNestedTypeLiteral(typeExpression: JSDocTypeExpression, name: EntityName) { @@ -6815,18 +6805,28 @@ namespace ts { if (!tagName) { return false; } + let t: PropertyLikeParse; switch (tagName.escapedText) { case "type": return target === PropertyLikeParse.Property && parseTypeTag(atToken, tagName); case "prop": case "property": - return target === PropertyLikeParse.Property && parseParameterOrPropertyTag(atToken, tagName, target); + t = PropertyLikeParse.Property; + break; case "arg": case "argument": case "param": - return target === PropertyLikeParse.Parameter && parseParameterOrPropertyTag(atToken, tagName, target); + t = PropertyLikeParse.Parameter; + break; + default: + return false; } - return false; + if (target !== t) { + return false; + } + const tag = parseParameterOrPropertyTag(atToken, tagName, target); + tag.comment = parseTagComments(tag.end - tag.pos); + return tag; } function parseTemplateTag(atToken: AtToken, tagName: Identifier): JSDocTemplateTag | undefined { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 37b62fd5d85..8304320b301 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1586,42 +1586,35 @@ namespace ts { ((node as JSDocFunctionType).parameters[0].name as Identifier).escapedText === "new"; } - export function getAllJSDocs(node: Node): (JSDoc | JSDocTag)[] { - if (isJSDocTypedefTag(node)) { - return [node.parent]; - } - return getJSDocCommentsAndTags(node); - } - - export function getSourceOfAssignment(node: Node): Node { + function getSourceOfAssignment(node: Node): Node { return isExpressionStatement(node) && node.expression && isBinaryExpression(node.expression) && node.expression.operatorToken.kind === SyntaxKind.EqualsToken && node.expression.right; } - export function getSingleInitializerOfVariableStatement(node: Node, child?: Node): Node { + function getSingleInitializerOfVariableStatement(node: Node, child?: Node): Node { return isVariableStatement(node) && node.declarationList.declarations.length > 0 && (!child || node.declarationList.declarations[0].initializer === child) && node.declarationList.declarations[0].initializer; } - export function getSingleVariableOfVariableStatement(node: Node, child?: Node): Node { + function getSingleVariableOfVariableStatement(node: Node, child?: Node): Node { return isVariableStatement(node) && node.declarationList.declarations.length > 0 && (!child || node.declarationList.declarations[0] === child) && node.declarationList.declarations[0]; } - export function getNestedModuleDeclaration(node: Node): Node { + function getNestedModuleDeclaration(node: Node): Node { return node.kind === SyntaxKind.ModuleDeclaration && (node as ModuleDeclaration).body && (node as ModuleDeclaration).body.kind === SyntaxKind.ModuleDeclaration && (node as ModuleDeclaration).body; } - export function getJSDocCommentsAndTags(node: Node): (JSDoc | JSDocTag)[] { + export function getJSDocCommentsAndTags(node: Node): ReadonlyArray { let result: (JSDoc | JSDocTag)[] | undefined; getJSDocCommentsAndTagsWorker(node); return result || emptyArray; diff --git a/src/services/jsDoc.ts b/src/services/jsDoc.ts index 46f2458bba9..91774e2290c 100644 --- a/src/services/jsDoc.ts +++ b/src/services/jsDoc.ts @@ -52,20 +52,30 @@ namespace ts.JsDoc { // Eg. const a: Array | Array; a.length // The property length will have two declarations of property length coming // from Array - Array and Array - const documentationComment = []; + const documentationComment: SymbolDisplayPart[] = []; forEachUnique(declarations, declaration => { - forEach(getAllJSDocs(declaration), doc => { - if (doc.comment) { - if (documentationComment.length) { - documentationComment.push(lineBreakPart()); - } - documentationComment.push(textPart(doc.comment)); + for (const { comment } of getCommentHavingNodes(declaration)) { + if (comment === undefined) continue; + if (documentationComment.length) { + documentationComment.push(lineBreakPart()); } - }); + documentationComment.push(textPart(comment)); + } }); return documentationComment; } + function getCommentHavingNodes(declaration: Declaration): ReadonlyArray { + switch (declaration.kind) { + case SyntaxKind.JSDocPropertyTag: + return [declaration as JSDocPropertyTag]; + case SyntaxKind.JSDocTypedefTag: + return [(declaration as JSDocTypedefTag).parent]; + default: + return getJSDocCommentsAndTags(declaration); + } + } + export function getJsDocTagsFromDeclarations(declarations?: Declaration[]): JSDocTagInfo[] { // Only collect doc comments from duplicate declarations once. const tags: JSDocTagInfo[] = []; @@ -77,7 +87,7 @@ namespace ts.JsDoc { return tags; } - function getCommentText(tag: JSDocTag): string { + function getCommentText(tag: JSDocTag): string | undefined { const { comment } = tag; switch (tag.kind) { case SyntaxKind.JSDocAugmentsTag: @@ -96,11 +106,15 @@ namespace ts.JsDoc { } function withNode(node: Node) { - return `${node.getText()} ${comment}`; + return addComment(node.getText()); } function withList(list: NodeArray): string { - return `${list.map(x => x.getText())} ${comment}`; + return addComment(list.map(x => x.getText()).join(", ")); + } + + function addComment(s: string) { + return comment === undefined ? s : `${s} ${comment}`; } } diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.leadingAsterisk.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.leadingAsterisk.json index 934de3d8faf..a21f9f81c44 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.leadingAsterisk.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.leadingAsterisk.json @@ -27,8 +27,7 @@ "pos": 15, "end": 21 } - }, - "comment": "" + } }, "length": 1, "pos": 8, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.noLeadingAsterisk.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.noLeadingAsterisk.json index 934de3d8faf..a21f9f81c44 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.noLeadingAsterisk.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.noLeadingAsterisk.json @@ -27,8 +27,7 @@ "pos": 15, "end": 21 } - }, - "comment": "" + } }, "length": 1, "pos": 8, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.noReturnType.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.noReturnType.json index 62228eac4af..079d09c6eeb 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.noReturnType.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.noReturnType.json @@ -17,8 +17,7 @@ "pos": 9, "end": 15, "escapedText": "return" - }, - "comment": "" + } }, "length": 1, "pos": 8, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.oneParamTag.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.oneParamTag.json index 15e8b4a5cfb..4940bcf325e 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.oneParamTag.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.oneParamTag.json @@ -35,8 +35,7 @@ "escapedText": "name1" }, "isNameFirst": false, - "isBracketed": false, - "comment": "" + "isBracketed": false }, "length": 1, "pos": 8, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagNameThenType1.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagNameThenType1.json index 93f47686e8f..7a1c85b25d6 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagNameThenType1.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramTagNameThenType1.json @@ -35,8 +35,7 @@ "escapedText": "name1" }, "isNameFirst": true, - "isBracketed": false, - "comment": "" + "isBracketed": false }, "length": 1, "pos": 8, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramWithoutType.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramWithoutType.json index 926344175d7..3d511525c64 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramWithoutType.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.paramWithoutType.json @@ -25,8 +25,7 @@ "escapedText": "foo" }, "isNameFirst": true, - "isBracketed": false, - "comment": "" + "isBracketed": false }, "length": 1, "pos": 8, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.returnTag1.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.returnTag1.json index 0668e7d6324..e02a0a38bb4 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.returnTag1.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.returnTag1.json @@ -27,8 +27,7 @@ "pos": 17, "end": 23 } - }, - "comment": "" + } }, "length": 1, "pos": 8, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.returnsTag1.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.returnsTag1.json index e108287c52a..70497ee8c1b 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.returnsTag1.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.returnsTag1.json @@ -27,8 +27,7 @@ "pos": 18, "end": 24 } - }, - "comment": "" + } }, "length": 1, "pos": 8, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag.json index b39b16497e6..8a146e3cfaf 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag.json @@ -33,8 +33,7 @@ "length": 1, "pos": 18, "end": 20 - }, - "comment": "" + } }, "length": 1, "pos": 8, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag2.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag2.json index 5eeb97af119..5bb1df30665 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag2.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag2.json @@ -44,8 +44,7 @@ "length": 2, "pos": 18, "end": 22 - }, - "comment": "" + } }, "length": 1, "pos": 8, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag3.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag3.json index 96645551c5c..295b2122daa 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag3.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag3.json @@ -44,8 +44,7 @@ "length": 2, "pos": 18, "end": 23 - }, - "comment": "" + } }, "length": 1, "pos": 8, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag4.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag4.json index 0b2719f59ba..4aa29db3092 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag4.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag4.json @@ -44,8 +44,7 @@ "length": 2, "pos": 18, "end": 23 - }, - "comment": "" + } }, "length": 1, "pos": 8, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag5.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag5.json index 8b6118e9b19..5e707f6f03b 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag5.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.templateTag5.json @@ -44,8 +44,7 @@ "length": 2, "pos": 18, "end": 24 - }, - "comment": "" + } }, "length": 1, "pos": 8, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.twoParamTag2.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.twoParamTag2.json index 391ee1aac2f..c73009315bc 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.twoParamTag2.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.twoParamTag2.json @@ -35,8 +35,7 @@ "escapedText": "name1" }, "isNameFirst": false, - "isBracketed": false, - "comment": "" + "isBracketed": false }, "1": { "kind": "JSDocParameterTag", @@ -70,8 +69,7 @@ "escapedText": "name2" }, "isNameFirst": false, - "isBracketed": false, - "comment": "" + "isBracketed": false }, "length": 2, "pos": 8, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.twoParamTagOnSameLine.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.twoParamTagOnSameLine.json index 628d54d162d..e1ef0adb926 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.twoParamTagOnSameLine.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.twoParamTagOnSameLine.json @@ -35,8 +35,7 @@ "escapedText": "name1" }, "isNameFirst": false, - "isBracketed": false, - "comment": "" + "isBracketed": false }, "1": { "kind": "JSDocParameterTag", @@ -70,8 +69,7 @@ "escapedText": "name2" }, "isNameFirst": false, - "isBracketed": false, - "comment": "" + "isBracketed": false }, "length": 2, "pos": 8, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typeTag.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typeTag.json index 934de3d8faf..a21f9f81c44 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typeTag.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typeTag.json @@ -27,8 +27,7 @@ "pos": 15, "end": 21 } - }, - "comment": "" + } }, "length": 1, "pos": 8, diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typedefTagWithChildrenTags.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typedefTagWithChildrenTags.json index 08d270286b9..a4a69fc48a6 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typedefTagWithChildrenTags.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typedefTagWithChildrenTags.json @@ -104,8 +104,7 @@ "isBracketed": false } ] - }, - "comment": "" + } }, "length": 1, "pos": 8, diff --git a/tests/baselines/reference/jsDocTags.baseline b/tests/baselines/reference/jsDocTags.baseline index 7f9e9fdce98..f316a9c4245 100644 --- a/tests/baselines/reference/jsDocTags.baseline +++ b/tests/baselines/reference/jsDocTags.baseline @@ -268,8 +268,7 @@ "documentation": [], "tags": [ { - "name": "mytag", - "text": "" + "name": "mytag" } ] } @@ -446,8 +445,7 @@ "text": "Another value" }, { - "name": "mytag", - "text": "" + "name": "mytag" } ] } @@ -580,12 +578,10 @@ "text": "here all the comments are on a new line" }, { - "name": "mytag3", - "text": "" + "name": "mytag3" }, { - "name": "mytag", - "text": "" + "name": "mytag" } ] } @@ -655,8 +651,7 @@ "documentation": [], "tags": [ { - "name": "mytag", - "text": "" + "name": "mytag" } ] } diff --git a/tests/baselines/reference/jsDocTypeTag1.js b/tests/baselines/reference/jsDocTypeTag1.js index 75333c69364..657ff5031a2 100644 --- a/tests/baselines/reference/jsDocTypeTag1.js +++ b/tests/baselines/reference/jsDocTypeTag1.js @@ -41,7 +41,7 @@ "tags": [ { "name": "type", - "text": "{String} " + "text": "{String}" } ] } @@ -88,7 +88,7 @@ "tags": [ { "name": "type", - "text": "{Number} " + "text": "{Number}" } ] } @@ -135,7 +135,7 @@ "tags": [ { "name": "type", - "text": "{Boolean} " + "text": "{Boolean}" } ] } @@ -182,7 +182,7 @@ "tags": [ { "name": "type", - "text": "{Void} " + "text": "{Void}" } ] } @@ -229,7 +229,7 @@ "tags": [ { "name": "type", - "text": "{Undefined} " + "text": "{Undefined}" } ] } @@ -276,7 +276,7 @@ "tags": [ { "name": "type", - "text": "{Null} " + "text": "{Null}" } ] } @@ -331,7 +331,7 @@ "tags": [ { "name": "type", - "text": "{Array} " + "text": "{Array}" } ] } @@ -390,7 +390,7 @@ "tags": [ { "name": "type", - "text": "{Promise} " + "text": "{Promise}" } ] } @@ -437,7 +437,7 @@ "tags": [ { "name": "type", - "text": "{Object} " + "text": "{Object}" } ] } @@ -484,7 +484,7 @@ "tags": [ { "name": "type", - "text": "{Function} " + "text": "{Function}" } ] } @@ -531,7 +531,7 @@ "tags": [ { "name": "type", - "text": "{*} " + "text": "{*}" } ] } @@ -578,7 +578,7 @@ "tags": [ { "name": "type", - "text": "{?} " + "text": "{?}" } ] } @@ -641,7 +641,7 @@ "tags": [ { "name": "type", - "text": "{String|Number} " + "text": "{String|Number}" } ] } diff --git a/tests/baselines/reference/jsDocTypeTag2.js b/tests/baselines/reference/jsDocTypeTag2.js index 01032a5cc09..9933360b009 100644 --- a/tests/baselines/reference/jsDocTypeTag2.js +++ b/tests/baselines/reference/jsDocTypeTag2.js @@ -41,7 +41,7 @@ "tags": [ { "name": "type", - "text": "{string} " + "text": "{string}" } ] } @@ -88,7 +88,7 @@ "tags": [ { "name": "type", - "text": "{number} " + "text": "{number}" } ] } @@ -135,7 +135,7 @@ "tags": [ { "name": "type", - "text": "{boolean} " + "text": "{boolean}" } ] } @@ -182,7 +182,7 @@ "tags": [ { "name": "type", - "text": "{void} " + "text": "{void}" } ] } @@ -229,7 +229,7 @@ "tags": [ { "name": "type", - "text": "{undefined} " + "text": "{undefined}" } ] } @@ -276,7 +276,7 @@ "tags": [ { "name": "type", - "text": "{null} " + "text": "{null}" } ] } @@ -331,7 +331,7 @@ "tags": [ { "name": "type", - "text": "{array} " + "text": "{array}" } ] } @@ -390,7 +390,7 @@ "tags": [ { "name": "type", - "text": "{promise} " + "text": "{promise}" } ] } @@ -437,7 +437,7 @@ "tags": [ { "name": "type", - "text": "{?number} " + "text": "{?number}" } ] } @@ -484,7 +484,7 @@ "tags": [ { "name": "type", - "text": "{function} " + "text": "{function}" } ] } @@ -567,7 +567,7 @@ "tags": [ { "name": "type", - "text": "{function (number): number} " + "text": "{function (number): number}" } ] } @@ -630,7 +630,7 @@ "tags": [ { "name": "type", - "text": "{string | number} " + "text": "{string | number}" } ] } diff --git a/tests/baselines/reference/jsDocTypedef1.js b/tests/baselines/reference/jsDocTypedef1.js index 96a86a4f7a8..e1b34f14c2b 100644 --- a/tests/baselines/reference/jsDocTypedef1.js +++ b/tests/baselines/reference/jsDocTypedef1.js @@ -181,7 +181,7 @@ "tags": [ { "name": "param", - "text": "opts " + "text": "opts" } ] } diff --git a/tests/baselines/reference/quickInfoJsDocTags.baseline b/tests/baselines/reference/quickInfoJsDocTags.baseline index ba225209760..56f190b4446 100644 --- a/tests/baselines/reference/quickInfoJsDocTags.baseline +++ b/tests/baselines/reference/quickInfoJsDocTags.baseline @@ -63,7 +63,7 @@ ], "documentation": [ { - "text": "Doc{T} A template", + "text": "DocT} A template", "kind": "text" } ], @@ -82,7 +82,7 @@ }, { "name": "typedef", - "text": "NumOrStr " + "text": "NumOrStr" }, { "name": "property", diff --git a/tests/cases/fourslash/jsDocTags.ts b/tests/cases/fourslash/jsDocTags.ts index 368939ecbfc..14ac14e6331 100644 --- a/tests/cases/fourslash/jsDocTags.ts +++ b/tests/cases/fourslash/jsDocTags.ts @@ -67,7 +67,7 @@ verify.currentSignatureHelpTagsAre([{name: "myjsdoctag", text:"this is a comment goTo.marker("11"); verify.currentSignatureHelpTagsAre([{name: "mytag", text:"comment1 comment2"}]) goTo.marker("12"); -verify.currentSignatureHelpTagsAre([{name: "mytag", text:""}]) +verify.currentSignatureHelpTagsAre([{name: "mytag"}]) goTo.marker("13"); verify.currentSignatureHelpTagsAre([{ name: "returns", text: "a value" }]) diff --git a/tests/cases/fourslash/quickInfoPropertyTag.ts b/tests/cases/fourslash/quickInfoPropertyTag.ts new file mode 100644 index 00000000000..b413a7610e1 --- /dev/null +++ b/tests/cases/fourslash/quickInfoPropertyTag.ts @@ -0,0 +1,16 @@ +/// + +// @allowJs: true + +// @Filename: /a.js +/////** +//// * @typedef I +//// * @property {number} x Doc +//// * More doc +//// */ +//// +/////** @type {I} */ +////const obj = { /**/x: 10 }; + +// TODO: GH#21123 There shouldn't be a " " before "More doc" +verify.quickInfoAt("", "(property) x: number", "Doc\n More doc"); From c5ed8646e18e22c4323c474cb690bc42dee9c39c Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 11 Jan 2018 12:33:38 -0800 Subject: [PATCH 064/172] Add test where emit of the file results in invalidating resolution and hence invoking recompile --- src/compiler/watch.ts | 4 ++++ src/harness/unittests/tscWatchMode.ts | 33 ++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index b4ed1c1ac1b..ccbf169c3a2 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -20,6 +20,9 @@ namespace ts { // Callbacks to do custom action before creating program and after creating program beforeCompile(compilerOptions: CompilerOptions): void; afterCompile(host: DirectoryStructureHost, program: Program, builder: Builder): void; + + // Only for testing + maxNumberOfFilesToIterateForInvalidation?: number; } const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = sys ? { @@ -301,6 +304,7 @@ namespace ts { hasChangedAutomaticTypeDirectiveNames = true; scheduleProgramUpdate(); }, + maxNumberOfFilesToIterateForInvalidation: watchingHost.maxNumberOfFilesToIterateForInvalidation, writeLog }; // Cache for the module resolution diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index 758a55b8f95..096778300e1 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -30,8 +30,9 @@ namespace ts.tscWatch { return ts.parseConfigFile(configFileName, {}, watchingSystemHost.system, watchingSystemHost.reportDiagnostic, watchingSystemHost.reportWatchDiagnostic); } - function createWatchModeWithConfigFile(configFilePath: string, host: WatchedSystem) { + function createWatchModeWithConfigFile(configFilePath: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) { const watchingSystemHost = createWatchingSystemHost(host); + watchingSystemHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation; const configFileResult = parseConfigFile(configFilePath, watchingSystemHost); return ts.createWatchModeWithConfigFile(configFileResult, {}, watchingSystemHost); } @@ -1067,6 +1068,36 @@ namespace ts.tscWatch { assert.equal(nowErrors[0].start, intialErrors[0].start - configFileContentComment.length); assert.equal(nowErrors[1].start, intialErrors[1].start - configFileContentComment.length); }); + + it("should not trigger recompilation because of program emit", () => { + const proj = "/user/username/projects/myproject"; + const file1: FileOrFolder = { + path: `${proj}/file1.ts`, + content: "export const c = 30;" + }; + const file2: FileOrFolder = { + path: `${proj}/src/file2.ts`, + content: `import {c} from "file1"; export const d = 30;` + }; + const tsconfig: FileOrFolder = { + path: `${proj}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { + module: "amd", + outDir: "build" + } + }) + }; + const host = createWatchedSystem([file1, file2, libFile, tsconfig], { currentDirectory: proj }); + const watch = createWatchModeWithConfigFile(tsconfig.path, host, /*maxNumberOfFilesToIterateForInvalidation*/1); + checkProgramActualFiles(watch(), [file1.path, file2.path, libFile.path]); + + assert.isTrue(host.fileExists("build/file1.js")); + assert.isTrue(host.fileExists("build/src/file2.js")); + + // This should be 0 + host.checkTimeoutQueueLengthAndRun(1); + }); }); describe("tsc-watch emit with outFile or out setting", () => { From 69bb5ea8f06348c8068b65b81679633d7a0ee297 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 11 Jan 2018 11:52:46 -0800 Subject: [PATCH 065/172] Do not trigger the failed lookup location invalidation for creation of program emit files Handles #20934 --- src/compiler/emitter.ts | 14 ++++++++++---- src/compiler/program.ts | 17 ++++++++++++++++- src/compiler/resolutionCache.ts | 6 ++++++ src/compiler/types.ts | 2 ++ src/compiler/watch.ts | 7 ++++++- src/compiler/watchUtilities.ts | 8 ++++++++ src/harness/unittests/tscWatchMode.ts | 2 +- src/server/project.ts | 5 ++++- 8 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 909dd0feede..aee8c98db0d 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -18,8 +18,8 @@ namespace ts { * If an array, the full list of source files to emit. * Else, calls `getSourceFilesToEmit` with the (optional) target source file to determine the list of source files to emit. */ - export function forEachEmittedFile( - host: EmitHost, action: (emitFileNames: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle, emitOnlyDtsFiles: boolean) => void, + export function forEachEmittedFile( + host: EmitHost, action: (emitFileNames: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle, emitOnlyDtsFiles: boolean) => T, sourceFilesOrTargetSourceFile?: SourceFile[] | SourceFile, emitOnlyDtsFiles?: boolean) { @@ -30,7 +30,10 @@ namespace ts { const jsFilePath = options.outFile || options.out; const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options); const declarationFilePath = options.declaration ? removeFileExtension(jsFilePath) + Extension.Dts : ""; - action({ jsFilePath, sourceMapFilePath, declarationFilePath }, createBundle(sourceFiles), emitOnlyDtsFiles); + const result = action({ jsFilePath, sourceMapFilePath, declarationFilePath }, createBundle(sourceFiles), emitOnlyDtsFiles); + if (result) { + return result; + } } } else { @@ -38,7 +41,10 @@ namespace ts { const jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, getOutputExtension(sourceFile, options)); const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options); const declarationFilePath = !isSourceFileJavaScript(sourceFile) && (emitOnlyDtsFiles || options.declaration) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined; - action({ jsFilePath, sourceMapFilePath, declarationFilePath }, sourceFile, emitOnlyDtsFiles); + const result = action({ jsFilePath, sourceMapFilePath, declarationFilePath }, sourceFile, emitOnlyDtsFiles); + if (result) { + return result; + } } } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 88a2f5ee8f1..0d1bbf233a3 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -667,7 +667,8 @@ namespace ts { dropDiagnosticsProducingTypeChecker, getSourceFileFromReference, sourceFileToPackageName, - redirectTargetsSet + redirectTargetsSet, + isEmittedFile }; verifyCompilerOptions(); @@ -2343,6 +2344,20 @@ namespace ts { hasEmitBlockingDiagnostics.set(toPath(emitFileName), true); programDiagnostics.add(diag); } + + function isEmittedFile(file: string) { + if (options.noEmit) { + return false; + } + + return forEachEmittedFile(getEmitHost(), ({ jsFilePath, declarationFilePath }) => + isSameFile(jsFilePath, file) || + (declarationFilePath && isSameFile(declarationFilePath, file))); + } + + function isSameFile(file1: string, file2: string) { + return comparePaths(file1, file2, currentDirectory, !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; + } } /* @internal */ diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index e21e81a2e88..aac25c5d596 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -52,6 +52,7 @@ namespace ts { getGlobalCache?(): string | undefined; writeLog(s: string): void; maxNumberOfFilesToIterateForInvalidation?: number; + getCurrentProgram(): Program; } interface DirectoryWatchesOfFailedLookup { @@ -472,6 +473,11 @@ namespace ts { resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); } + // Ignore emits from the program + if (isEmittedFileOfProgram(resolutionHost.getCurrentProgram(), fileOrDirectory)) { + return; + } + // If the files are added to project root or node_modules directory, always run through the invalidation process // Otherwise run through invalidation only if adding to the immediate directory if (!allFilesHaveInvalidatedResolution && diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 23798635671..e4ada136730 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2659,6 +2659,8 @@ namespace ts { /* @internal */ sourceFileToPackageName: Map; /** Set of all source files that some other source file redirects to. */ /* @internal */ redirectTargetsSet: Map; + /** Is the file emitted file */ + /* @internal */ isEmittedFile(file: string): boolean; } /* @internal */ diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index ccbf169c3a2..79e26f5986c 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -305,6 +305,7 @@ namespace ts { scheduleProgramUpdate(); }, maxNumberOfFilesToIterateForInvalidation: watchingHost.maxNumberOfFilesToIterateForInvalidation, + getCurrentProgram, writeLog }; // Cache for the module resolution @@ -322,7 +323,11 @@ namespace ts { // Update the wild card directory watch watchConfigFileWildCardDirectories(); - return () => program; + return getCurrentProgram; + + function getCurrentProgram() { + return program; + } function synchronizeProgram() { writeLog(`Synchronizing program`); diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index 0cf38f372d9..0bc6920479b 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -82,6 +82,14 @@ namespace ts { } } + export function isEmittedFileOfProgram(program: Program | undefined, file: string) { + if (!program) { + return false; + } + + return program.isEmittedFile(file); + } + export function addFileWatcher(host: System, file: string, cb: FileWatcherCallback): FileWatcher { return host.watchFile(file, cb); } diff --git a/src/harness/unittests/tscWatchMode.ts b/src/harness/unittests/tscWatchMode.ts index 096778300e1..9e01021155c 100644 --- a/src/harness/unittests/tscWatchMode.ts +++ b/src/harness/unittests/tscWatchMode.ts @@ -1096,7 +1096,7 @@ namespace ts.tscWatch { assert.isTrue(host.fileExists("build/src/file2.js")); // This should be 0 - host.checkTimeoutQueueLengthAndRun(1); + host.checkTimeoutQueueLengthAndRun(0); }); }); diff --git a/src/server/project.ts b/src/server/project.ts index ebc93ae3e1e..a25e6870bda 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -830,7 +830,10 @@ namespace ts.server { return !hasChanges; } - + /* @internal */ + getCurrentProgram() { + return this.program; + } protected removeExistingTypings(include: string[]): string[] { const existing = ts.getAutomaticTypeDirectiveNames(this.getCompilerOptions(), this.directoryStructureHost); From 269867f5e83004b0ef1e7a54290dfc1bc1c75d5d Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 11 Jan 2018 13:42:08 -0800 Subject: [PATCH 066/172] Fix bug: get merged symbol of symbol.parent before checking against module symbol (#21147) --- src/services/completions.ts | 2 +- ...port_named_exportEqualsNamespace_merged.ts | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/completionsImport_named_exportEqualsNamespace_merged.ts diff --git a/src/services/completions.ts b/src/services/completions.ts index bdd5e4a86a2..ac1eb8f6a46 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1200,7 +1200,7 @@ namespace ts.Completions { // Don't add a completion for a re-export, only for the original. // If `symbol.parent !== ...`, this comes from an `export * from "foo"` re-export. Those don't create new symbols. // If `some(...)`, this comes from an `export { foo } from "foo"` re-export, which creates a new symbol (thus isn't caught by the first check). - if (symbol.parent !== typeChecker.resolveExternalModuleSymbol(moduleSymbol) + if (typeChecker.getMergedSymbol(symbol.parent) !== typeChecker.resolveExternalModuleSymbol(moduleSymbol) || some(symbol.declarations, d => isExportSpecifier(d) && !!d.parent.parent.moduleSpecifier)) { continue; } diff --git a/tests/cases/fourslash/completionsImport_named_exportEqualsNamespace_merged.ts b/tests/cases/fourslash/completionsImport_named_exportEqualsNamespace_merged.ts new file mode 100644 index 00000000000..55b33c22114 --- /dev/null +++ b/tests/cases/fourslash/completionsImport_named_exportEqualsNamespace_merged.ts @@ -0,0 +1,21 @@ +/// + +// @Filename: /b.d.ts +////declare namespace N { +//// export const foo: number; +////} +////declare module "n" { +//// export = N; +////} + +// @Filename: /c.d.ts +////declare namespace N {} + +// @Filename: /a.ts +////fo/**/ + +goTo.marker(""); +verify.completionListContains({ name: "foo", source: "n" }, "const N.foo: number", "", "const", undefined, /*hasAction*/ true, { + includeExternalModuleExports: true, + sourceDisplay: "n", +}); From c75f328697f9a7357c7993d75481c8a1b8db38e1 Mon Sep 17 00:00:00 2001 From: csigs Date: Thu, 11 Jan 2018 23:10:17 +0000 Subject: [PATCH 067/172] LEGO: check in for master to temporary branch. --- .../diagnosticMessages.generated.json.lcl | 118 +++++++++++++++++- .../diagnosticMessages.generated.json.lcl | 118 +++++++++++++++++- 2 files changed, 232 insertions(+), 4 deletions(-) diff --git a/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl index 30850814f26..66702cf999d 100644 --- a/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/csy/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -483,6 +483,15 @@ + + + + + + + + + @@ -885,6 +894,12 @@ + + + + + + @@ -1290,6 +1305,24 @@ + + + + + + + + + + + + + + + + + + @@ -1839,6 +1872,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2307,6 +2367,15 @@ + + + + + + + + + @@ -2904,6 +2973,15 @@ + + + + + + + + + @@ -4548,6 +4626,12 @@ + + + + + + @@ -5583,6 +5667,15 @@ + + + + + + + + + @@ -5940,6 +6033,15 @@ + + + + + + + + + @@ -8004,6 +8106,15 @@ + + + + + + + + + @@ -8507,10 +8618,13 @@ - + - + + + + diff --git a/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl index 76488c39e76..2ac1ab23150 100644 --- a/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/jpn/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -474,6 +474,15 @@ + + + + + + + + + @@ -876,6 +885,12 @@ + + + + + + @@ -1281,6 +1296,24 @@ + + + + + + + + + + + + + + + + + + @@ -1830,6 +1863,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2298,6 +2358,15 @@ + + + + + + + + + @@ -2895,6 +2964,15 @@ + + + + + + + + + @@ -4539,6 +4617,12 @@ + + + + + + @@ -5574,6 +5658,15 @@ + + + + + + + + + @@ -5931,6 +6024,15 @@ + + + + + + + + + @@ -7995,6 +8097,15 @@ + + + + + + + + + @@ -8498,10 +8609,13 @@ - + - + + + + From 1ae0b461f88e9a68c2b2ad51519611a2cc292fec Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 11 Jan 2018 16:14:19 -0800 Subject: [PATCH 068/172] Use contra-variant inferences when co-variant inferences yield 'never' --- src/compiler/checker.ts | 50 ++++++++++++++++++++++++++--------------- src/compiler/types.ts | 21 +++++++++-------- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fd6a033653a..665b61be66b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11344,6 +11344,7 @@ namespace ts { return { typeParameter, candidates: undefined, + contraCandidates: undefined, inferredType: undefined, priority: undefined, topLevel: true, @@ -11355,6 +11356,7 @@ namespace ts { return { typeParameter: inference.typeParameter, candidates: inference.candidates && inference.candidates.slice(), + contraCandidates: inference.contraCandidates && inference.contraCandidates.slice(), inferredType: inference.inferredType, priority: inference.priority, topLevel: inference.topLevel, @@ -11468,6 +11470,7 @@ namespace ts { function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0) { let symbolStack: Symbol[]; let visited: Map; + let contravariant = false; inferFromTypes(originalSource, originalTarget); function inferFromTypes(source: Type, target: Type) { @@ -11535,18 +11538,20 @@ namespace ts { const inference = getInferenceInfoForType(target); if (inference) { if (!inference.isFixed) { - // We give lowest priority to inferences of implicitNeverType (which is used as the - // element type for empty array literals). Thus, inferences from empty array literals - // only matter when no other inferences are made. - const p = priority | (source === implicitNeverType ? InferencePriority.NeverType : 0); - if (!inference.candidates || p < inference.priority) { - inference.candidates = [source]; - inference.priority = p; + if (inference.priority === undefined || priority < inference.priority) { + inference.candidates = undefined; + inference.contraCandidates = undefined; + inference.priority = priority; } - else if (p === inference.priority) { - inference.candidates.push(source); + if (priority === inference.priority) { + if (contravariant) { + inference.contraCandidates = append(inference.contraCandidates, source); + } + else { + inference.candidates = append(inference.candidates, source); + } } - if (!(p & InferencePriority.ReturnType) && target.flags & TypeFlags.TypeParameter && !isTypeParameterAtTopLevel(originalTarget, target)) { + if (!(priority & InferencePriority.ReturnType) && target.flags & TypeFlags.TypeParameter && !isTypeParameterAtTopLevel(originalTarget, target)) { inference.topLevel = false; } } @@ -11569,15 +11574,15 @@ namespace ts { } } else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) { - priority ^= InferencePriority.Contravariant; + contravariant = !contravariant; inferFromTypes((source).type, (target).type); - priority ^= InferencePriority.Contravariant; + contravariant = !contravariant; } else if ((isLiteralType(source) || source.flags & TypeFlags.String) && target.flags & TypeFlags.Index) { const empty = createEmptyObjectTypeFromStringLiteral(source); - priority ^= InferencePriority.Contravariant; + contravariant = !contravariant; inferFromTypes(empty, (target as IndexType).type); - priority ^= InferencePriority.Contravariant; + contravariant = !contravariant; } else if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) { inferFromTypes((source).objectType, (target).objectType); @@ -11646,9 +11651,9 @@ namespace ts { function inferFromContravariantTypes(source: Type, target: Type) { if (strictFunctionTypes) { - priority ^= InferencePriority.Contravariant; + contravariant = !contravariant; inferFromTypes(source, target); - priority ^= InferencePriority.Contravariant; + contravariant = !contravariant; } else { inferFromTypes(source, target); @@ -11827,10 +11832,19 @@ namespace ts { // If all inferences were made from contravariant positions, infer a common subtype. Otherwise, if // union types were requested or if all inferences were made from the return type position, infer a // union type. Otherwise, infer a common supertype. - const unwidenedType = inference.priority & InferencePriority.Contravariant ? getCommonSubtype(baseCandidates) : - context.flags & InferenceFlags.InferUnionTypes || inference.priority & InferencePriority.ReturnType ? getUnionType(baseCandidates, UnionReduction.Subtype) : + const unwidenedType = context.flags & InferenceFlags.InferUnionTypes || inference.priority & InferencePriority.ReturnType ? + getUnionType(baseCandidates, UnionReduction.Subtype) : getCommonSupertype(baseCandidates); inferredType = getWidenedType(unwidenedType); + // If we have inferred 'never' but have contravariant candidates. To get a more specific type we + // infer from the contravariant candidates instead. + if (inferredType.flags & TypeFlags.Never && inference.contraCandidates) { + inferredType = getCommonSubtype(inference.contraCandidates); + } + } + else if (inference.contraCandidates) { + // We only have contravariant inferences, infer the best common subtype of those + inferredType = getCommonSubtype(inference.contraCandidates); } else if (context.flags & InferenceFlags.NoDefault) { // We use silentNeverType as the wildcard that signals no inferences. diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 23798635671..7c9a7da69e6 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3772,20 +3772,19 @@ namespace ts { export type TypeMapper = (t: TypeParameter) => Type; export const enum InferencePriority { - Contravariant = 1 << 0, // Inference from contravariant position - NakedTypeVariable = 1 << 1, // Naked type variable in union or intersection type - MappedType = 1 << 2, // Reverse inference for mapped type - ReturnType = 1 << 3, // Inference made from return type of generic function - NeverType = 1 << 4, // Inference made from the never type + NakedTypeVariable = 1 << 0, // Naked type variable in union or intersection type + MappedType = 1 << 1, // Reverse inference for mapped type + ReturnType = 1 << 2, // Inference made from return type of generic function } export interface InferenceInfo { - typeParameter: TypeParameter; - candidates: Type[]; - inferredType: Type; - priority: InferencePriority; - topLevel: boolean; - isFixed: boolean; + typeParameter: TypeParameter; // Type parameter for which inferences are being made + candidates: Type[]; // Candidates in covariant positions (or undefined) + contraCandidates: Type[]; // Candidates in contravariant positions (or undefined) + inferredType: Type; // Cache for resolved inferred type + priority: InferencePriority; // Priority of current inference set + topLevel: boolean; // True if all inferences are to top level occurrences + isFixed: boolean; // True if inferences are fixed } export const enum InferenceFlags { From 6b882c7b39c061bbd2e250508f0b58320c699244 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 11 Jan 2018 16:14:45 -0800 Subject: [PATCH 069/172] Accept new baselines --- tests/baselines/reference/api/tsserverlibrary.d.ts | 9 ++++----- tests/baselines/reference/api/typescript.d.ts | 9 ++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 3eabea4435a..1aac7c893c5 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2149,15 +2149,14 @@ declare namespace ts { declaration?: SignatureDeclaration; } enum InferencePriority { - Contravariant = 1, - NakedTypeVariable = 2, - MappedType = 4, - ReturnType = 8, - NeverType = 16, + NakedTypeVariable = 1, + MappedType = 2, + ReturnType = 4, } interface InferenceInfo { typeParameter: TypeParameter; candidates: Type[]; + contraCandidates: Type[]; inferredType: Type; priority: InferencePriority; topLevel: boolean; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 630b7a08a28..3bf033798f9 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2149,15 +2149,14 @@ declare namespace ts { declaration?: SignatureDeclaration; } enum InferencePriority { - Contravariant = 1, - NakedTypeVariable = 2, - MappedType = 4, - ReturnType = 8, - NeverType = 16, + NakedTypeVariable = 1, + MappedType = 2, + ReturnType = 4, } interface InferenceInfo { typeParameter: TypeParameter; candidates: Type[]; + contraCandidates: Type[]; inferredType: Type; priority: InferencePriority; topLevel: boolean; From 12b80f3183cd97aacf448c29d609aa4b56be260c Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 11 Jan 2018 16:26:38 -0800 Subject: [PATCH 070/172] Keep string index from intersections in base constraint of index type --- src/compiler/checker.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d402f344d60..a32104c0535 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6468,7 +6468,7 @@ namespace ts { } function getConstraintOfIndexedAccess(type: IndexedAccessType) { - const transformed = getSimplifiedIndexedAccessType(type); + const transformed = getSubstitutedIndexedMappedType(type); if (transformed) { return transformed; } @@ -8383,6 +8383,11 @@ namespace ts { getIntersectionType(stringIndexTypes) ]); } + return getSubstitutedIndexedMappedType(type); + } + + function getSubstitutedIndexedMappedType(type: IndexedAccessType): Type { + const objectType = type.objectType; // If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper // that substitutes the index type for P. For example, for an index access { [P in K]: Box }[X], we // construct the type Box. From baf31ec52e500bd646090f62fcb4b6112bb09645 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 11 Jan 2018 16:30:05 -0800 Subject: [PATCH 071/172] Test Diff and Omit --- .../indexedAccessRetainsIndexSignature.js | 10 ++++++ ...indexedAccessRetainsIndexSignature.symbols | 33 +++++++++++++++++++ .../indexedAccessRetainsIndexSignature.types | 33 +++++++++++++++++++ .../indexedAccessRetainsIndexSignature.ts | 6 ++++ 4 files changed, 82 insertions(+) create mode 100644 tests/baselines/reference/indexedAccessRetainsIndexSignature.js create mode 100644 tests/baselines/reference/indexedAccessRetainsIndexSignature.symbols create mode 100644 tests/baselines/reference/indexedAccessRetainsIndexSignature.types create mode 100644 tests/cases/compiler/indexedAccessRetainsIndexSignature.ts diff --git a/tests/baselines/reference/indexedAccessRetainsIndexSignature.js b/tests/baselines/reference/indexedAccessRetainsIndexSignature.js new file mode 100644 index 00000000000..6c33317f156 --- /dev/null +++ b/tests/baselines/reference/indexedAccessRetainsIndexSignature.js @@ -0,0 +1,10 @@ +//// [indexedAccessRetainsIndexSignature.ts] +type Diff = + ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T] +type Omit = Pick> + + +type O = Omit<{ a: number, b: string }, 'a'> + + +//// [indexedAccessRetainsIndexSignature.js] diff --git a/tests/baselines/reference/indexedAccessRetainsIndexSignature.symbols b/tests/baselines/reference/indexedAccessRetainsIndexSignature.symbols new file mode 100644 index 00000000000..908d74c9452 --- /dev/null +++ b/tests/baselines/reference/indexedAccessRetainsIndexSignature.symbols @@ -0,0 +1,33 @@ +=== tests/cases/compiler/indexedAccessRetainsIndexSignature.ts === +type Diff = +>Diff : Symbol(Diff, Decl(indexedAccessRetainsIndexSignature.ts, 0, 0)) +>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 0, 10)) +>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 0, 27)) + + ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T] +>P : Symbol(P, Decl(indexedAccessRetainsIndexSignature.ts, 1, 8)) +>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 0, 10)) +>P : Symbol(P, Decl(indexedAccessRetainsIndexSignature.ts, 1, 8)) +>P : Symbol(P, Decl(indexedAccessRetainsIndexSignature.ts, 1, 26)) +>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 0, 27)) +>x : Symbol(x, Decl(indexedAccessRetainsIndexSignature.ts, 1, 48)) +>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 0, 10)) + +type Omit = Pick> +>Omit : Symbol(Omit, Decl(indexedAccessRetainsIndexSignature.ts, 1, 71)) +>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 2, 10)) +>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 2, 12)) +>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 2, 10)) +>Pick : Symbol(Pick, Decl(lib.d.ts, --, --)) +>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 2, 10)) +>Diff : Symbol(Diff, Decl(indexedAccessRetainsIndexSignature.ts, 0, 0)) +>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 2, 10)) +>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 2, 12)) + + +type O = Omit<{ a: number, b: string }, 'a'> +>O : Symbol(O, Decl(indexedAccessRetainsIndexSignature.ts, 2, 59)) +>Omit : Symbol(Omit, Decl(indexedAccessRetainsIndexSignature.ts, 1, 71)) +>a : Symbol(a, Decl(indexedAccessRetainsIndexSignature.ts, 5, 15)) +>b : Symbol(b, Decl(indexedAccessRetainsIndexSignature.ts, 5, 26)) + diff --git a/tests/baselines/reference/indexedAccessRetainsIndexSignature.types b/tests/baselines/reference/indexedAccessRetainsIndexSignature.types new file mode 100644 index 00000000000..8ef6cb62559 --- /dev/null +++ b/tests/baselines/reference/indexedAccessRetainsIndexSignature.types @@ -0,0 +1,33 @@ +=== tests/cases/compiler/indexedAccessRetainsIndexSignature.ts === +type Diff = +>Diff : ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[T] +>T : T +>U : U + + ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T] +>P : P +>T : T +>P : P +>P : P +>U : U +>x : string +>T : T + +type Omit = Pick> +>Omit : Pick +>U : U +>K : K +>U : U +>Pick : Pick +>U : U +>Diff : ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[T] +>U : U +>K : K + + +type O = Omit<{ a: number, b: string }, 'a'> +>O : Pick<{ a: number; b: string; }, "b"> +>Omit : Pick +>a : number +>b : string + diff --git a/tests/cases/compiler/indexedAccessRetainsIndexSignature.ts b/tests/cases/compiler/indexedAccessRetainsIndexSignature.ts new file mode 100644 index 00000000000..ff38b2d9090 --- /dev/null +++ b/tests/cases/compiler/indexedAccessRetainsIndexSignature.ts @@ -0,0 +1,6 @@ +type Diff = + ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T] +type Omit = Pick> + + +type O = Omit<{ a: number, b: string }, 'a'> From 13bf022ef67cb0f5a6a73d2a9a3aa2867305c7f7 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 11 Jan 2018 16:45:44 -0800 Subject: [PATCH 072/172] Add regression tests --- tests/cases/compiler/strictFunctionTypes1.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/cases/compiler/strictFunctionTypes1.ts b/tests/cases/compiler/strictFunctionTypes1.ts index 6c07bc51ffe..ad8836fcf05 100644 --- a/tests/cases/compiler/strictFunctionTypes1.ts +++ b/tests/cases/compiler/strictFunctionTypes1.ts @@ -17,3 +17,13 @@ const x1 = f1(fo, fs); // (x: string) => void const x2 = f2("abc", fo, fs); // "abc" const x3 = f3("abc", fo, fx); // "abc" | "def" const x4 = f4(fo, fs); // Func + +declare const never: never; + +const x10 = f2(never, fo, fs); // string +const x11 = f3(never, fo, fx); // "def" + +// Repro from #21112 + +declare function foo(a: ReadonlyArray): T; +let x = foo([]); // never From b6b936f2df617872e2ef9ee9f7346ef88e8f5792 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 11 Jan 2018 16:45:54 -0800 Subject: [PATCH 073/172] Accept new baselines --- .../reference/strictFunctionTypes1.js | 18 ++++++++++ .../reference/strictFunctionTypes1.symbols | 31 ++++++++++++++++ .../reference/strictFunctionTypes1.types | 35 +++++++++++++++++++ 3 files changed, 84 insertions(+) diff --git a/tests/baselines/reference/strictFunctionTypes1.js b/tests/baselines/reference/strictFunctionTypes1.js index e802c443561..94ee06f88f8 100644 --- a/tests/baselines/reference/strictFunctionTypes1.js +++ b/tests/baselines/reference/strictFunctionTypes1.js @@ -15,6 +15,16 @@ const x1 = f1(fo, fs); // (x: string) => void const x2 = f2("abc", fo, fs); // "abc" const x3 = f3("abc", fo, fx); // "abc" | "def" const x4 = f4(fo, fs); // Func + +declare const never: never; + +const x10 = f2(never, fo, fs); // string +const x11 = f3(never, fo, fx); // "def" + +// Repro from #21112 + +declare function foo(a: ReadonlyArray): T; +let x = foo([]); // never //// [strictFunctionTypes1.js] @@ -23,6 +33,9 @@ var x1 = f1(fo, fs); // (x: string) => void var x2 = f2("abc", fo, fs); // "abc" var x3 = f3("abc", fo, fx); // "abc" | "def" var x4 = f4(fo, fs); // Func +var x10 = f2(never, fo, fs); // string +var x11 = f3(never, fo, fx); // "def" +var x = foo([]); // never //// [strictFunctionTypes1.d.ts] @@ -40,3 +53,8 @@ declare const x1: (x: string) => void; declare const x2 = "abc"; declare const x3: string; declare const x4: Func; +declare const never: never; +declare const x10: string; +declare const x11: "def"; +declare function foo(a: ReadonlyArray): T; +declare let x: never; diff --git a/tests/baselines/reference/strictFunctionTypes1.symbols b/tests/baselines/reference/strictFunctionTypes1.symbols index 70253411201..715ea287734 100644 --- a/tests/baselines/reference/strictFunctionTypes1.symbols +++ b/tests/baselines/reference/strictFunctionTypes1.symbols @@ -94,3 +94,34 @@ const x4 = f4(fo, fs); // Func >fo : Symbol(fo, Decl(strictFunctionTypes1.ts, 6, 58)) >fs : Symbol(fs, Decl(strictFunctionTypes1.ts, 8, 37)) +declare const never: never; +>never : Symbol(never, Decl(strictFunctionTypes1.ts, 17, 13)) + +const x10 = f2(never, fo, fs); // string +>x10 : Symbol(x10, Decl(strictFunctionTypes1.ts, 19, 5)) +>f2 : Symbol(f2, Decl(strictFunctionTypes1.ts, 0, 79)) +>never : Symbol(never, Decl(strictFunctionTypes1.ts, 17, 13)) +>fo : Symbol(fo, Decl(strictFunctionTypes1.ts, 6, 58)) +>fs : Symbol(fs, Decl(strictFunctionTypes1.ts, 8, 37)) + +const x11 = f3(never, fo, fx); // "def" +>x11 : Symbol(x11, Decl(strictFunctionTypes1.ts, 20, 5)) +>f3 : Symbol(f3, Decl(strictFunctionTypes1.ts, 1, 74)) +>never : Symbol(never, Decl(strictFunctionTypes1.ts, 17, 13)) +>fo : Symbol(fo, Decl(strictFunctionTypes1.ts, 6, 58)) +>fx : Symbol(fx, Decl(strictFunctionTypes1.ts, 9, 37)) + +// Repro from #21112 + +declare function foo(a: ReadonlyArray): T; +>foo : Symbol(foo, Decl(strictFunctionTypes1.ts, 20, 30)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 24, 21)) +>a : Symbol(a, Decl(strictFunctionTypes1.ts, 24, 24)) +>ReadonlyArray : Symbol(ReadonlyArray, Decl(lib.d.ts, --, --)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 24, 21)) +>T : Symbol(T, Decl(strictFunctionTypes1.ts, 24, 21)) + +let x = foo([]); // never +>x : Symbol(x, Decl(strictFunctionTypes1.ts, 25, 3)) +>foo : Symbol(foo, Decl(strictFunctionTypes1.ts, 20, 30)) + diff --git a/tests/baselines/reference/strictFunctionTypes1.types b/tests/baselines/reference/strictFunctionTypes1.types index 9701d78cff0..b915e0b5f6b 100644 --- a/tests/baselines/reference/strictFunctionTypes1.types +++ b/tests/baselines/reference/strictFunctionTypes1.types @@ -100,3 +100,38 @@ const x4 = f4(fo, fs); // Func >fo : (x: Object) => void >fs : (x: string) => void +declare const never: never; +>never : never + +const x10 = f2(never, fo, fs); // string +>x10 : string +>f2(never, fo, fs) : string +>f2 : (obj: T, f1: (x: T) => void, f2: (x: T) => void) => T +>never : never +>fo : (x: Object) => void +>fs : (x: string) => void + +const x11 = f3(never, fo, fx); // "def" +>x11 : "def" +>f3(never, fo, fx) : "def" +>f3 : (obj: T, f1: (x: T) => void, f2: (f: (x: T) => void) => void) => T +>never : never +>fo : (x: Object) => void +>fx : (f: (x: "def") => void) => void + +// Repro from #21112 + +declare function foo(a: ReadonlyArray): T; +>foo : (a: ReadonlyArray) => T +>T : T +>a : ReadonlyArray +>ReadonlyArray : ReadonlyArray +>T : T +>T : T + +let x = foo([]); // never +>x : never +>foo([]) : never +>foo : (a: ReadonlyArray) => T +>[] : never[] + From ef7f13139838101e305a716b3fe1bdeba9a1804f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 11 Jan 2018 15:45:05 -0800 Subject: [PATCH 074/172] Add test case for symLink and rename --- .../unittests/tsserverProjectSystem.ts | 107 ++++++++++++++ src/harness/virtualFileSystemWithWatch.ts | 139 +++++++++++++++--- 2 files changed, 224 insertions(+), 22 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index fe7a2095b86..2811584bf1b 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -6560,4 +6560,111 @@ namespace ts.projectSystem { checkProjectActualFiles(project, [file.path]); }); }); + + describe("tsserverProjectSystem with symLinks", () => { + it("rename in common file renames all project", () => { + const projects = "/users/username/projects"; + const folderA = `${projects}/a`; + const aFile: FileOrFolder = { + path: `${folderA}/a.ts`, + content: `import {C} from "./c/fc"; console.log(C)` + }; + const aTsconfig: FileOrFolder = { + path: `${folderA}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { module: "commonjs" } }) + }; + const aC: FileOrFolder = { + path: `${folderA}/c`, + symLink: "../c" + }; + const aFc = `${folderA}/c/fc.ts`; + + const folderB = `${projects}/b`; + const bFile: FileOrFolder = { + path: `${folderB}/b.ts`, + content: `import {C} from "./c/fc"; console.log(C)` + }; + const bTsconfig: FileOrFolder = { + path: `${folderB}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { module: "commonjs" } }) + }; + const bC: FileOrFolder = { + path: `${folderB}/c`, + symLink: "../c" + }; + const bFc = `${folderB}/c/fc.ts`; + + const folderC = `${projects}/c`; + const cFile: FileOrFolder = { + path: `${folderC}/fc.ts`, + content: `export const C = 8` + }; + + const files = [cFile, libFile, aFile, aTsconfig, aC, bFile, bTsconfig, bC]; + const host = createServerHost(files); + const session = createSession(host); + const projectService = session.getProjectService(); + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { + file: aFile.path, + projectRootPath: folderA + } + }); + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { + file: bFile.path, + projectRootPath: folderB + } + }); + + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { + file: aFc, + projectRootPath: folderA + } + }); + session.executeCommandSeq({ + command: protocol.CommandTypes.Open, + arguments: { + file: bFc, + projectRootPath: folderB + } + }); + checkNumberOfProjects(projectService, { configuredProjects: 2 }); + assert.isDefined(projectService.configuredProjects.get(aTsconfig.path)); + assert.isDefined(projectService.configuredProjects.get(bTsconfig.path)); + + verifyRenameResponse(session.executeCommandSeq({ + command: protocol.CommandTypes.Rename, + arguments: { + file: aFc, + line: 1, + offset: 14, + findInStrings: false, + findInComments: false + } + }).response as protocol.RenameResponseBody); + + function verifyRenameResponse({ info, locs }: protocol.RenameResponseBody) { + assert.isTrue(info.canRename); + assert.equal(locs.length, 2); // Currently 2 but needs to be 4 + assert.deepEqual(locs[0], { + file: aFile.path, + locs: [ + { start: { line: 1, offset: 39 }, end: { line: 1, offset: 40 } }, + { start: { line: 1, offset: 9 }, end: { line: 1, offset: 10 } } + ] + }); + assert.deepEqual(locs[1], { + file: aFc, + locs: [ + { start: { line: 1, offset: 14 }, end: { line: 1, offset: 15 } } + ] + }); + } + }); + }); } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 921d4674231..5b326789247 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -70,6 +70,7 @@ interface Array {}` path: string; content?: string; fileSize?: number; + symLink?: string; } interface FSEntry { @@ -86,6 +87,10 @@ interface Array {}` entries: FSEntry[]; } + interface SymLink extends FSEntry { + symLink: Path; + } + function isFolder(s: FSEntry): s is Folder { return s && isArray((s).entries); } @@ -94,6 +99,10 @@ interface Array {}` return s && isString((s).content); } + function isSymLink(s: FSEntry): s is SymLink { + return s && isString((s).symLink); + } + function invokeWatcherCallbacks(callbacks: T[], invokeCallback: (cb: T) => void): void { if (callbacks) { // The array copy is made to ensure that even if one of the callback removes the callbacks, @@ -316,9 +325,12 @@ interface Array {}` } } else { - // TODO: Changing from file => folder + // TODO: Changing from file => folder/Symlink } } + else if (isSymLink(currentEntry)) { + // TODO: update symlinks + } else { // Folder if (isString(fileOrDirectory.content)) { @@ -339,7 +351,7 @@ interface Array {}` // If this entry is not from the new file or folder if (!mapNewLeaves.get(path)) { // Leaf entries that arent in new list => remove these - if (isFile(fileOrDirectory) || isFolder(fileOrDirectory) && fileOrDirectory.entries.length === 0) { + if (isFile(fileOrDirectory) || isSymLink(fileOrDirectory) || isFolder(fileOrDirectory) && fileOrDirectory.entries.length === 0) { this.removeFileOrFolder(fileOrDirectory, folder => !mapNewLeaves.get(folder.path)); } } @@ -387,6 +399,12 @@ interface Array {}` const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath)); this.addFileOrFolderInFolder(baseFolder, file, ignoreWatchInvokedWithTriggerAsFileCreate); } + else if (isString(fileOrDirectory.symLink)) { + const symLink = this.toSymLink(fileOrDirectory); + Debug.assert(!this.fs.get(symLink.path)); + const baseFolder = this.ensureFolder(getDirectoryPath(symLink.fullPath)); + this.addFileOrFolderInFolder(baseFolder, symLink, ignoreWatchInvokedWithTriggerAsFileCreate); + } else { const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory); this.ensureFolder(fullPath); @@ -414,20 +432,20 @@ interface Array {}` return folder; } - private addFileOrFolderInFolder(folder: Folder, fileOrDirectory: File | Folder, ignoreWatch?: boolean) { + private addFileOrFolderInFolder(folder: Folder, fileOrDirectory: File | Folder | SymLink, ignoreWatch?: boolean) { folder.entries.push(fileOrDirectory); this.fs.set(fileOrDirectory.path, fileOrDirectory); if (ignoreWatch) { return; } - if (isFile(fileOrDirectory)) { + if (isFile(fileOrDirectory) || isSymLink(fileOrDirectory)) { this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Created); } this.invokeDirectoryWatcher(folder.fullPath, fileOrDirectory.fullPath); } - private removeFileOrFolder(fileOrDirectory: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean, isRenaming?: boolean) { + private removeFileOrFolder(fileOrDirectory: File | Folder | SymLink, isRemovableLeafFolder: (folder: Folder) => boolean, isRenaming?: boolean) { const basePath = getDirectoryPath(fileOrDirectory.path); const baseFolder = this.fs.get(basePath) as Folder; if (basePath !== fileOrDirectory.path) { @@ -436,7 +454,7 @@ interface Array {}` } this.fs.delete(fileOrDirectory.path); - if (isFile(fileOrDirectory)) { + if (isFile(fileOrDirectory) || isSymLink(fileOrDirectory)) { this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted); } else { @@ -503,6 +521,15 @@ interface Array {}` }; } + private toSymLink(fileOrDirectory: FileOrFolder): SymLink { + const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory); + return { + path: this.toPath(fullPath), + fullPath, + symLink: this.toPath(getNormalizedAbsolutePath(fileOrDirectory.symLink, getDirectoryPath(fullPath))) + }; + } + private toFolder(path: string): Folder { const fullPath = getNormalizedAbsolutePath(path, this.currentDirectory); return { @@ -512,14 +539,70 @@ interface Array {}` }; } - fileExists(s: string) { - const path = this.toFullPath(s); - return isFile(this.fs.get(path)); + private isFile(fsEntry: FSEntry) { + return !!this.getRealFile(fsEntry.path, fsEntry); } - readFile(s: string) { - const fsEntry = this.fs.get(this.toFullPath(s)); - return isFile(fsEntry) ? fsEntry.content : undefined; + private getRealFile(path: Path, fsEntry = this.fs.get(path)): File | undefined { + if (isFile(fsEntry)) { + return fsEntry; + } + + if (isSymLink(fsEntry)) { + return this.getRealFile(fsEntry.symLink); + } + + if (fsEntry) { + // This fs entry is something else + return undefined; + } + + const dir = getDirectoryPath(path); + const dirEntry = this.getRealFolder(dir); + if (dirEntry && dirEntry.path !== dir) { + return this.getRealFile(combinePaths(dirEntry.path, getBaseFileName(path)) as Path); + } + return undefined; + } + + private isFolder(fsEntry: FSEntry) { + return !!this.getRealFolder(fsEntry.path, fsEntry); + } + + private getRealFolder(path: Path, fsEntry = this.fs.get(path)): Folder | undefined { + if (isFolder(fsEntry)) { + return fsEntry; + } + + if (isSymLink(fsEntry)) { + return this.getRealFolder(fsEntry.symLink); + } + + if (fsEntry) { + // This fs entry is something else + return undefined; + } + + const baseName = getBaseFileName(path); + const dir = getDirectoryPath(path); + if (dir !== baseName) { + const dirEntry = this.getRealFolder(dir); + if (dirEntry && dirEntry.path !== dir) { + return this.getRealFolder(combinePaths(dirEntry.path, baseName) as Path); + } + } + + return undefined; + } + + fileExists(s: string) { + const path = this.toFullPath(s); + return !!this.getRealFile(path); + } + + readFile(s: string): string { + const fsEntry = this.getRealFile(this.toFullPath(s)); + return fsEntry ? fsEntry.content : undefined; } getFileSize(s: string) { @@ -533,14 +616,14 @@ interface Array {}` directoryExists(s: string) { const path = this.toFullPath(s); - return isFolder(this.fs.get(path)); + return !!this.getRealFolder(path); } - getDirectories(s: string) { + getDirectories(s: string): string[] { const path = this.toFullPath(s); - const folder = this.fs.get(path); - if (isFolder(folder)) { - return mapDefined(folder.entries, entry => isFolder(entry) ? getBaseFileName(entry.fullPath) : undefined); + const folder = this.getRealFolder(path); + if (folder) { + return mapDefined(folder.entries, entry => this.isFolder(entry) ? getBaseFileName(entry.fullPath) : undefined); } Debug.fail(folder ? "getDirectories called on file" : "getDirectories called on missing folder"); return []; @@ -550,13 +633,13 @@ interface Array {}` return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => { const directories: string[] = []; const files: string[] = []; - const dirEntry = this.fs.get(this.toPath(dir)); - if (isFolder(dirEntry)) { - dirEntry.entries.forEach((entry) => { - if (isFolder(entry)) { + const folder = this.getRealFolder(this.toPath(dir)); + if (folder) { + folder.entries.forEach((entry) => { + if (this.isFolder(entry)) { directories.push(getBaseFileName(entry.fullPath)); } - else if (isFile(entry)) { + else if (this.isFile(entry)) { files.push(getBaseFileName(entry.fullPath)); } else { @@ -682,6 +765,18 @@ interface Array {}` clear(this.output); } + realpath(s: string) { + while (true) { + const fsEntry = this.fs.get(this.toFullPath(s)); + if (isSymLink(fsEntry)) { + s = fsEntry.symLink; + } + else { + return s; + } + } + } + readonly existMessage = "System Exit"; exitCode: number; readonly resolvePath = (s: string) => s; From d2fd137d882b66a1f1dd73be5fd3570c4654fa85 Mon Sep 17 00:00:00 2001 From: Andy Date: Fri, 12 Jan 2018 10:44:39 -0800 Subject: [PATCH 075/172] Add a `getFullText()` helper method to `IScriptSnapshot` (#21155) * Add a `getFullText()` helper method to `IScriptSnapshot` * Use a function instead of a method --- src/harness/fourslash.ts | 6 +---- src/harness/harnessLanguageService.ts | 23 +++++-------------- src/harness/unittests/hostNewLineSupport.ts | 23 +++---------------- src/harness/unittests/incrementalParser.ts | 6 ++--- .../unittests/tsserverProjectSystem.ts | 11 ++++----- src/harness/unittests/versionCache.ts | 4 +--- src/server/client.ts | 3 +-- src/server/scriptInfo.ts | 3 +-- src/server/scriptVersionCache.ts | 2 +- src/server/session.ts | 4 +--- src/services/services.ts | 5 ++-- src/services/shims.ts | 6 ++--- src/services/utilities.ts | 4 ++++ 13 files changed, 31 insertions(+), 69 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 8c5570f730c..2e98a95831f 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -170,11 +170,7 @@ namespace FourSlash { // This function creates IScriptSnapshot object for testing getPreProcessedFileInfo // Return object may lack some functionalities for other purposes. function createScriptSnapShot(sourceText: string): ts.IScriptSnapshot { - return { - getText: (start: number, end: number) => sourceText.substr(start, end - start), - getLength: () => sourceText.length, - getChangeRange: () => undefined - }; + return ts.ScriptSnapshot.fromString(sourceText); } export class TestState { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index d51517c159b..0e142debedc 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -84,7 +84,7 @@ namespace Harness.LanguageService { } class ScriptSnapshotProxy implements ts.ScriptSnapshotShim { - constructor(public scriptSnapshot: ts.IScriptSnapshot) { + constructor(private readonly scriptSnapshot: ts.IScriptSnapshot) { } public getText(start: number, end: number): string { @@ -96,14 +96,8 @@ namespace Harness.LanguageService { } public getChangeRange(oldScript: ts.ScriptSnapshotShim): string { - const oldShim = oldScript; - - const range = this.scriptSnapshot.getChangeRange(oldShim.scriptSnapshot); - if (range === undefined) { - return undefined; - } - - return JSON.stringify({ span: { start: range.span.start, length: range.span.length }, newLength: range.newLength }); + const range = this.scriptSnapshot.getChangeRange((oldScript as ScriptSnapshotProxy).scriptSnapshot); + return range && JSON.stringify(range); } } @@ -236,12 +230,7 @@ namespace Harness.LanguageService { } readFile(path: string): string | undefined { const target = this.symlinks.get(path); - if (target !== undefined) { - return this.readFile(target); - } - - const snapshot = this.getScriptSnapshot(path); - return snapshot.getText(0, snapshot.getLength()); + return target !== undefined ? this.readFile(target) : ts.getSnapshotText(this.getScriptSnapshot(path)); } addSymlink(from: string, target: string) { this.symlinks.set(from, target); } realpath(path: string): string { @@ -350,7 +339,7 @@ namespace Harness.LanguageService { fileExists(fileName: string) { return this.getScriptInfo(fileName) !== undefined; } readFile(fileName: string) { const snapshot = this.nativeHost.getScriptSnapshot(fileName); - return snapshot && snapshot.getText(0, snapshot.getLength()); + return snapshot && ts.getSnapshotText(snapshot); } log(s: string): void { this.nativeHost.log(s); } trace(s: string): void { this.nativeHost.trace(s); } @@ -654,7 +643,7 @@ namespace Harness.LanguageService { } const snapshot = this.host.getScriptSnapshot(fileName); - return snapshot && snapshot.getText(0, snapshot.getLength()); + return snapshot && ts.getSnapshotText(snapshot); } writeFile = ts.noop; diff --git a/src/harness/unittests/hostNewLineSupport.ts b/src/harness/unittests/hostNewLineSupport.ts index 95a05f101f7..e7e5c11e33e 100644 --- a/src/harness/unittests/hostNewLineSupport.ts +++ b/src/harness/unittests/hostNewLineSupport.ts @@ -4,27 +4,10 @@ namespace ts { function testLSWithFiles(settings: CompilerOptions, files: Harness.Compiler.TestFile[]) { function snapFor(path: string): IScriptSnapshot { if (path === "lib.d.ts") { - return { - dispose: noop, - getChangeRange() { return undefined; }, - getLength() { return 0; }, - getText() { - return ""; - } - }; + return ScriptSnapshot.fromString(""); } - const result = forEach(files, f => f.unitName === path ? f : undefined); - if (result) { - return { - dispose: noop, - getChangeRange() { return undefined; }, - getLength() { return result.content.length; }, - getText(start, end) { - return result.content.substring(start, end); - } - }; - } - return undefined; + const result = find(files, f => f.unitName === path); + return result && ScriptSnapshot.fromString(result.content); } const lshost: LanguageServiceHost = { getCompilationSettings: () => settings, diff --git a/src/harness/unittests/incrementalParser.ts b/src/harness/unittests/incrementalParser.ts index c71b89d3da6..cdccce418bc 100644 --- a/src/harness/unittests/incrementalParser.ts +++ b/src/harness/unittests/incrementalParser.ts @@ -5,7 +5,7 @@ namespace ts { ts.disableIncrementalParsing = false; function withChange(text: IScriptSnapshot, start: number, length: number, newText: string): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } { - const contents = text.getText(0, text.getLength()); + const contents = getSnapshotText(text); const newContents = contents.substr(0, start) + newText + contents.substring(start + length); return { text: ScriptSnapshot.fromString(newContents), textChangeRange: createTextChangeRange(createTextSpan(start, length), newText.length) }; @@ -105,7 +105,7 @@ namespace ts { const newTextAndChange = withDelete(oldText, index, 1); const newTree = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1, oldTree).incrementalNewTree; - source = newTextAndChange.text.getText(0, newTextAndChange.text.getLength()); + source = getSnapshotText(newTextAndChange.text); oldTree = newTree; } } @@ -118,7 +118,7 @@ namespace ts { const newTextAndChange = withInsert(oldText, index + i, toInsert.charAt(i)); const newTree = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1, oldTree).incrementalNewTree; - source = newTextAndChange.text.getText(0, newTextAndChange.text.getLength()); + source = getSnapshotText(newTextAndChange.text); oldTree = newTree; } } diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index fe7a2095b86..565c20ac9cd 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -2116,7 +2116,7 @@ namespace ts.projectSystem { const scriptInfo = project.getScriptInfo(file1.path); const snap = scriptInfo.getSnapshot(); - const actualText = snap.getText(0, snap.getLength()); + const actualText = getSnapshotText(snap); assert.equal(actualText, "", `expected content to be empty string, got "${actualText}"`); projectService.openClientFile(file1.path, `var x = 1;`); @@ -2128,8 +2128,7 @@ namespace ts.projectSystem { projectService.closeClientFile(file1.path); const scriptInfo2 = project.getScriptInfo(file1.path); - const snap2 = scriptInfo2.getSnapshot(); - const actualText2 = snap2.getText(0, snap.getLength()); + const actualText2 = getSnapshotText(scriptInfo2.getSnapshot()); assert.equal(actualText2, "", `expected content to be empty string, got "${actualText2}"`); }); @@ -4178,7 +4177,7 @@ namespace ts.projectSystem { // verify content const projectServiice = session.getProjectService(); const snap1 = projectServiice.getScriptInfo(f1.path).getSnapshot(); - assert.equal(snap1.getText(0, snap1.getLength()), tmp.content, "content should be equal to the content of temp file"); + assert.equal(getSnapshotText(snap1), tmp.content, "content should be equal to the content of temp file"); // reload from original file file session.executeCommand({ @@ -4190,7 +4189,7 @@ namespace ts.projectSystem { // verify content const snap2 = projectServiice.getScriptInfo(f1.path).getSnapshot(); - assert.equal(snap2.getText(0, snap2.getLength()), f1.content, "content should be equal to the content of original file"); + assert.equal(getSnapshotText(snap2), f1.content, "content should be equal to the content of original file"); }); @@ -4280,7 +4279,7 @@ namespace ts.projectSystem { function checkScriptInfoContents(contentsOfInfo: string, captionForContents: string) { const snap = info.getSnapshot(); - assert.equal(snap.getText(0, snap.getLength()), contentsOfInfo, "content should be equal to " + captionForContents); + assert.equal(getSnapshotText(snap), contentsOfInfo, "content should be equal to " + captionForContents); } }); }); diff --git a/src/harness/unittests/versionCache.ts b/src/harness/unittests/versionCache.ts index bbd23f25dac..3062a55170c 100644 --- a/src/harness/unittests/versionCache.ts +++ b/src/harness/unittests/versionCache.ts @@ -284,9 +284,7 @@ and grew 1cm per day`; svc.edit(ersa[i], elas[i], insertString); checkText = editFlat(ersa[i], elas[i], insertString, checkText); if (0 === (i % 4)) { - const snap = svc.getSnapshot(); - const snapText = snap.getText(0, checkText.length); - assert.equal(checkText, snapText); + assert.equal(checkText, getSnapshotText(svc.getSnapshot())); } } }); diff --git a/src/server/client.ts b/src/server/client.ts index f34516ac4fd..7a64d9b528c 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -52,8 +52,7 @@ namespace ts.server { private getLineMap(fileName: string): number[] { let lineMap = this.lineMaps.get(fileName); if (!lineMap) { - const scriptSnapshot = this.host.getScriptSnapshot(fileName); - lineMap = computeLineStarts(scriptSnapshot.getText(0, scriptSnapshot.getLength())); + lineMap = computeLineStarts(getSnapshotText(this.host.getScriptSnapshot(fileName))); this.lineMaps.set(fileName, lineMap); } return lineMap; diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index f800a1117d0..6a722b5c3e0 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -370,8 +370,7 @@ namespace ts.server { } saveTo(fileName: string) { - const snap = this.textStorage.getSnapshot(); - this.host.writeFile(fileName, snap.getText(0, snap.getLength())); + this.host.writeFile(fileName, getSnapshotText(this.textStorage.getSnapshot())); } /*@internal*/ diff --git a/src/server/scriptVersionCache.ts b/src/server/scriptVersionCache.ts index 9650130634e..dccf4d3267b 100644 --- a/src/server/scriptVersionCache.ts +++ b/src/server/scriptVersionCache.ts @@ -371,7 +371,7 @@ namespace ts.server { } getLength() { - return this.index.root.charCount(); + return this.index.getLength(); } getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange { diff --git a/src/server/session.ts b/src/server/session.ts index 693d1651c91..b66bd83c19f 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1534,9 +1534,7 @@ namespace ts.server { let mappedRenameLocation: protocol.Location | undefined; if (renameFilename !== undefined && renameLocation !== undefined) { const renameScriptInfo = project.getScriptInfoForNormalizedPath(toNormalizedPath(renameFilename)); - const snapshot = renameScriptInfo.getSnapshot(); - const oldText = snapshot.getText(0, snapshot.getLength()); - mappedRenameLocation = getLocationInNewDocument(oldText, renameFilename, renameLocation, edits); + mappedRenameLocation = getLocationInNewDocument(getSnapshotText(renameScriptInfo.getSnapshot()), renameFilename, renameLocation, edits); } return { renameLocation: mappedRenameLocation, renameFilename, edits: this.mapTextChangesToCodeEdits(project, edits) }; } diff --git a/src/services/services.ts b/src/services/services.ts index eb5e3ae5743..90e9b64a1df 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1065,8 +1065,7 @@ namespace ts { } export function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile { - const text = scriptSnapshot.getText(0, scriptSnapshot.getLength()); - const sourceFile = createSourceFile(fileName, text, scriptTarget, setNodeParents, scriptKind); + const sourceFile = createSourceFile(fileName, getSnapshotText(scriptSnapshot), scriptTarget, setNodeParents, scriptKind); setSourceFileFields(sourceFile, scriptSnapshot, version); return sourceFile; } @@ -1265,7 +1264,7 @@ namespace ts { const path = toPath(fileName, currentDirectory, getCanonicalFileName); const entry = hostCache.getEntryByPath(path); if (entry) { - return isString(entry) ? undefined : entry.scriptSnapshot.getText(0, entry.scriptSnapshot.getLength()); + return isString(entry) ? undefined : getSnapshotText(entry.scriptSnapshot); } return host.readFile && host.readFile(fileName); }, diff --git a/src/services/shims.ts b/src/services/shims.ts index fda698785b3..30a836a113f 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -1083,7 +1083,7 @@ namespace ts { `getPreProcessedFileInfo('${fileName}')`, () => { // for now treat files as JavaScript - const result = preProcessFile(sourceTextSnapshot.getText(0, sourceTextSnapshot.getLength()), /* readImportFiles */ true, /* detectJavaScriptImports */ true); + const result = preProcessFile(getSnapshotText(sourceTextSnapshot), /* readImportFiles */ true, /* detectJavaScriptImports */ true); return { referencedFiles: this.convertFileReferences(result.referencedFiles), importedFiles: this.convertFileReferences(result.importedFiles), @@ -1123,9 +1123,7 @@ namespace ts { return this.forwardJSONCall( `getTSConfigFileInfo('${fileName}')`, () => { - const text = sourceTextSnapshot.getText(0, sourceTextSnapshot.getLength()); - - const result = parseJsonText(fileName, text); + const result = parseJsonText(fileName, getSnapshotText(sourceTextSnapshot)); const normalizedFileName = normalizeSlashes(fileName); const configFile = parseJsonSourceFileConfigFileContent(result, this.host, getDirectoryPath(normalizedFileName), /*existingOptions*/ {}, normalizedFileName); diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 8fe12d29313..9562def31f5 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1107,6 +1107,10 @@ namespace ts { seen.set(key, true); return true; } + + export function getSnapshotText(snap: IScriptSnapshot): string { + return snap.getText(0, snap.getLength()); + } } // Display-part writer helpers From b8acf8d0c461677a96aabe19ea5bd7614c5e9bb3 Mon Sep 17 00:00:00 2001 From: Andy Date: Fri, 12 Jan 2018 14:25:33 -0800 Subject: [PATCH 076/172] Handle jsconfig.json in fourslash tests (#16484) --- src/harness/fourslash.ts | 24 ++++++++++-------------- src/harness/harness.ts | 7 ++++++- tests/cases/fourslash/jsconfig.ts | 16 ++++++++++++++++ 3 files changed, 32 insertions(+), 15 deletions(-) create mode 100644 tests/cases/fourslash/jsconfig.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 2e98a95831f..4c0c72a200f 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -258,13 +258,13 @@ namespace FourSlash { let startResolveFileRef: FourSlashFile; let configFileName: string; - ts.forEach(testData.files, file => { + for (const file of testData.files) { // Create map between fileName and its content for easily looking up when resolveReference flag is specified this.inputFiles.set(file.fileName, file.content); - if (isTsconfig(file)) { + if (isConfig(file)) { const configJson = ts.parseConfigFileTextToJson(file.fileName, file.content); if (configJson.config === undefined) { - throw new Error(`Failed to parse test tsconfig.json: ${configJson.error.messageText}`); + throw new Error(`Failed to parse test ${file.fileName}: ${configJson.error.messageText}`); } // Extend our existing compiler options so that we can also support tsconfig only options @@ -286,7 +286,7 @@ namespace FourSlash { // If entry point for resolving file references is already specified, report duplication error throw new Error("There exists a Fourslash file which has resolveReference flag specified; remove duplicated resolveReference flag"); } - }); + } if (configFileName) { const baseDir = ts.normalizePath(ts.getDirectoryPath(configFileName)); @@ -295,12 +295,7 @@ namespace FourSlash { const configJsonObj = ts.parseConfigFileTextToJson(configFileName, this.inputFiles.get(configFileName)); assert.isTrue(configJsonObj.config !== undefined); - const { options, errors } = ts.parseJsonConfigFileContent(configJsonObj.config, host, baseDir); - - // Extend our existing compiler options so that we can also support tsconfig only options - if (!errors || errors.length === 0) { - compilationOptions = ts.extend(compilationOptions, options); - } + compilationOptions = ts.parseJsonConfigFileContent(configJsonObj.config, host, baseDir, compilationOptions, configFileName).options; } @@ -3401,13 +3396,14 @@ ${code} } // @Filename is the only directive that can be used in a test that contains tsconfig.json file. - if (files.some(isTsconfig)) { + const config = ts.find(files, isConfig); + if (config) { let directive = getNonFileNameOptionInFileList(files); if (!directive) { directive = getNonFileNameOptionInObject(globalOptions); } if (directive) { - throw Error("It is not allowed to use tsconfig.json along with directive '" + directive + "'"); + throw Error(`It is not allowed to use ${config.fileName} along with directive '${directive}'`); } } @@ -3420,8 +3416,8 @@ ${code} }; } - function isTsconfig(file: FourSlashFile): boolean { - return ts.getBaseFileName(file.fileName).toLowerCase() === "tsconfig.json"; + function isConfig(file: FourSlashFile): boolean { + return Harness.getConfigNameFromFileName(file.fileName) !== undefined; } function getNonFileNameOptionInFileList(files: FourSlashFile[]): string { diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 612224cb312..f2f8587d834 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1961,7 +1961,7 @@ namespace Harness { let tsConfigFileUnitData: TestUnitData; for (let i = 0; i < testUnitData.length; i++) { const data = testUnitData[i]; - if (ts.getBaseFileName(data.name).toLowerCase() === "tsconfig.json") { + if (getConfigNameFromFileName(data.name)) { const configJson = ts.parseJsonText(data.name, data.content); assert.isTrue(configJson.endOfFileToken !== undefined); let baseDir = ts.normalizePath(ts.getDirectoryPath(data.name)); @@ -2172,5 +2172,10 @@ namespace Harness { return { unitName: libFile, content: io.readFile(libFile) }; } + export function getConfigNameFromFileName(filename: string): "tsconfig.json" | "jsconfig.json" | undefined { + const flc = ts.getBaseFileName(filename).toLowerCase(); + return ts.find(["tsconfig.json" as "tsconfig.json", "jsconfig.json" as "jsconfig.json"], x => x === flc); + } + if (Error) (Error).stackTraceLimit = 100; } diff --git a/tests/cases/fourslash/jsconfig.ts b/tests/cases/fourslash/jsconfig.ts new file mode 100644 index 00000000000..ce09c46b857 --- /dev/null +++ b/tests/cases/fourslash/jsconfig.ts @@ -0,0 +1,16 @@ +/// + +// @Filename: /a.js +////function f(/**/x) { +////} + +// @Filename: /jsconfig.json +////{ +//// "compilerOptions": { +//// "checkJs": true, +//// "noImplicitAny": true +//// } +////} + +goTo.file("/a.js"); +verify.errorExistsAfterMarker(""); From d74820d51997d6a84edd9acfe2916e0090240f5b Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 12 Jan 2018 14:43:31 -0800 Subject: [PATCH 077/172] Remove mapped types to never from intersections when transforming an indexed access type in order to get its constraint. --- src/compiler/checker.ts | 25 ++++++++++--- .../indexedAccessRetainsIndexSignature.js | 5 +++ ...indexedAccessRetainsIndexSignature.symbols | 35 +++++++++++++++++-- .../indexedAccessRetainsIndexSignature.types | 31 ++++++++++++++++ .../indexedAccessRetainsIndexSignature.ts | 4 +++ 5 files changed, 92 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a32104c0535..1a5cc357c78 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6468,7 +6468,7 @@ namespace ts { } function getConstraintOfIndexedAccess(type: IndexedAccessType) { - const transformed = getSubstitutedIndexedMappedType(type); + const transformed = getSimplifiedIndexedAccessType(type); if (transformed) { return transformed; } @@ -8359,6 +8359,10 @@ namespace ts { return false; } + function isMappedTypeToNever(type: Type) { + return getObjectFlags(type) & ObjectFlags.Mapped && getTemplateTypeFromMappedType(type as MappedType) === neverType; + } + // Transform an indexed access to a simpler form, if possible. Return the simpler form, or return // undefined if no transformation is possible. function getSimplifiedIndexedAccessType(type: IndexedAccessType): Type { @@ -8383,11 +8387,22 @@ namespace ts { getIntersectionType(stringIndexTypes) ]); } - return getSubstitutedIndexedMappedType(type); - } + // Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or + // more mapped types with a template type `never`, '(U & V & { [P in T]: never })[K]', return a + // transformed type that removes the never-mapped type: '(U & V)[K]'. This mirrors what would happen + // eventually anyway, but it easier to reason about. + if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType) && some((objectType).types, isMappedTypeToNever)) { + let nonNeverTypes: Type[]; + for (const t of (objectType).types) { + if (!isMappedTypeToNever(t)) { + (nonNeverTypes || (nonNeverTypes = [])).push(t); + } + } + if (nonNeverTypes) { + return getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType); + } + } - function getSubstitutedIndexedMappedType(type: IndexedAccessType): Type { - const objectType = type.objectType; // If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper // that substitutes the index type for P. For example, for an index access { [P in K]: Box }[X], we // construct the type Box. diff --git a/tests/baselines/reference/indexedAccessRetainsIndexSignature.js b/tests/baselines/reference/indexedAccessRetainsIndexSignature.js index 6c33317f156..1b77b95527b 100644 --- a/tests/baselines/reference/indexedAccessRetainsIndexSignature.js +++ b/tests/baselines/reference/indexedAccessRetainsIndexSignature.js @@ -2,9 +2,14 @@ type Diff = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T] type Omit = Pick> +type Omit1 = Pick>; +// is in fact an equivalent of +type Omit2 = {[P in Diff]: T[P]}; type O = Omit<{ a: number, b: string }, 'a'> +const o: O = { b: '' } //// [indexedAccessRetainsIndexSignature.js] +var o = { b: '' }; diff --git a/tests/baselines/reference/indexedAccessRetainsIndexSignature.symbols b/tests/baselines/reference/indexedAccessRetainsIndexSignature.symbols index 908d74c9452..9858f6fc162 100644 --- a/tests/baselines/reference/indexedAccessRetainsIndexSignature.symbols +++ b/tests/baselines/reference/indexedAccessRetainsIndexSignature.symbols @@ -24,10 +24,39 @@ type Omit = Pick> >U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 2, 10)) >K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 2, 12)) +type Omit1 = Pick>; +>Omit1 : Symbol(Omit1, Decl(indexedAccessRetainsIndexSignature.ts, 2, 59)) +>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 3, 11)) +>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 3, 13)) +>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 3, 11)) +>Pick : Symbol(Pick, Decl(lib.d.ts, --, --)) +>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 3, 11)) +>Diff : Symbol(Diff, Decl(indexedAccessRetainsIndexSignature.ts, 0, 0)) +>U : Symbol(U, Decl(indexedAccessRetainsIndexSignature.ts, 3, 11)) +>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 3, 13)) + +// is in fact an equivalent of + +type Omit2 = {[P in Diff]: T[P]}; +>Omit2 : Symbol(Omit2, Decl(indexedAccessRetainsIndexSignature.ts, 3, 61)) +>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 6, 11)) +>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 6, 13)) +>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 6, 11)) +>P : Symbol(P, Decl(indexedAccessRetainsIndexSignature.ts, 6, 37)) +>Diff : Symbol(Diff, Decl(indexedAccessRetainsIndexSignature.ts, 0, 0)) +>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 6, 11)) +>K : Symbol(K, Decl(indexedAccessRetainsIndexSignature.ts, 6, 13)) +>T : Symbol(T, Decl(indexedAccessRetainsIndexSignature.ts, 6, 11)) +>P : Symbol(P, Decl(indexedAccessRetainsIndexSignature.ts, 6, 37)) type O = Omit<{ a: number, b: string }, 'a'> ->O : Symbol(O, Decl(indexedAccessRetainsIndexSignature.ts, 2, 59)) +>O : Symbol(O, Decl(indexedAccessRetainsIndexSignature.ts, 6, 67)) >Omit : Symbol(Omit, Decl(indexedAccessRetainsIndexSignature.ts, 1, 71)) ->a : Symbol(a, Decl(indexedAccessRetainsIndexSignature.ts, 5, 15)) ->b : Symbol(b, Decl(indexedAccessRetainsIndexSignature.ts, 5, 26)) +>a : Symbol(a, Decl(indexedAccessRetainsIndexSignature.ts, 8, 15)) +>b : Symbol(b, Decl(indexedAccessRetainsIndexSignature.ts, 8, 26)) + +const o: O = { b: '' } +>o : Symbol(o, Decl(indexedAccessRetainsIndexSignature.ts, 9, 5)) +>O : Symbol(O, Decl(indexedAccessRetainsIndexSignature.ts, 6, 67)) +>b : Symbol(b, Decl(indexedAccessRetainsIndexSignature.ts, 9, 14)) diff --git a/tests/baselines/reference/indexedAccessRetainsIndexSignature.types b/tests/baselines/reference/indexedAccessRetainsIndexSignature.types index 8ef6cb62559..3e3f52f7ca6 100644 --- a/tests/baselines/reference/indexedAccessRetainsIndexSignature.types +++ b/tests/baselines/reference/indexedAccessRetainsIndexSignature.types @@ -24,6 +24,30 @@ type Omit = Pick> >U : U >K : K +type Omit1 = Pick>; +>Omit1 : Pick +>U : U +>K : K +>U : U +>Pick : Pick +>U : U +>Diff : ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[T] +>U : U +>K : K + +// is in fact an equivalent of + +type Omit2 = {[P in Diff]: T[P]}; +>Omit2 : Omit2 +>T : T +>K : K +>T : T +>P : P +>Diff : ({ [P in T]: P; } & { [P in U]: never; } & { [x: string]: never; })[T] +>T : T +>K : K +>T : T +>P : P type O = Omit<{ a: number, b: string }, 'a'> >O : Pick<{ a: number; b: string; }, "b"> @@ -31,3 +55,10 @@ type O = Omit<{ a: number, b: string }, 'a'> >a : number >b : string +const o: O = { b: '' } +>o : Pick<{ a: number; b: string; }, "b"> +>O : Pick<{ a: number; b: string; }, "b"> +>{ b: '' } : { b: string; } +>b : string +>'' : "" + diff --git a/tests/cases/compiler/indexedAccessRetainsIndexSignature.ts b/tests/cases/compiler/indexedAccessRetainsIndexSignature.ts index ff38b2d9090..d23cbe87ddf 100644 --- a/tests/cases/compiler/indexedAccessRetainsIndexSignature.ts +++ b/tests/cases/compiler/indexedAccessRetainsIndexSignature.ts @@ -1,6 +1,10 @@ type Diff = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T] type Omit = Pick> +type Omit1 = Pick>; +// is in fact an equivalent of +type Omit2 = {[P in Diff]: T[P]}; type O = Omit<{ a: number, b: string }, 'a'> +const o: O = { b: '' } From f1622f0dc6b75a727ce8f0c2e2ef05a33dd31bbd Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 12 Jan 2018 14:56:50 -0800 Subject: [PATCH 078/172] Use filter instead of unnecessary laziness --- src/compiler/checker.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1a5cc357c78..811fe868f96 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8389,18 +8389,11 @@ namespace ts { } // Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or // more mapped types with a template type `never`, '(U & V & { [P in T]: never })[K]', return a - // transformed type that removes the never-mapped type: '(U & V)[K]'. This mirrors what would happen + // transformed type that removes the never-mapped type: '(U & V)[K]'. This mirrors what would happen // eventually anyway, but it easier to reason about. if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType) && some((objectType).types, isMappedTypeToNever)) { - let nonNeverTypes: Type[]; - for (const t of (objectType).types) { - if (!isMappedTypeToNever(t)) { - (nonNeverTypes || (nonNeverTypes = [])).push(t); - } - } - if (nonNeverTypes) { - return getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType); - } + const nonNeverTypes = filter((objectType).types, t => !isMappedTypeToNever(t)); + return getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType); } // If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper From fd1bb9bde21f79bc90325990129f45dc3b0185a0 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 12 Jan 2018 15:07:46 -0800 Subject: [PATCH 079/172] Group intersection code in getSimplifiedIndexedAccessType --- src/compiler/checker.ts | 52 +++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 811fe868f96..5e26b37b61a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8367,33 +8367,35 @@ namespace ts { // undefined if no transformation is possible. function getSimplifiedIndexedAccessType(type: IndexedAccessType): Type { const objectType = type.objectType; - // Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or - // more object types with only a string index signature, e.g. '(U & V & { [x: string]: D })[K]', return a - // transformed type of the form '(U & V)[K] | D'. This allows us to properly reason about higher order indexed - // access types with default property values as expressed by D. - if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType) && some((objectType).types, isStringIndexOnlyType)) { - const regularTypes: Type[] = []; - const stringIndexTypes: Type[] = []; - for (const t of (objectType).types) { - if (isStringIndexOnlyType(t)) { - stringIndexTypes.push(getIndexTypeOfType(t, IndexKind.String)); - } - else { - regularTypes.push(t); + if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType)) { + // Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or + // more object types with only a string index signature, e.g. '(U & V & { [x: string]: D })[K]', return a + // transformed type of the form '(U & V)[K] | D'. This allows us to properly reason about higher order indexed + // access types with default property values as expressed by D. + if (some((objectType).types, isStringIndexOnlyType)) { + const regularTypes: Type[] = []; + const stringIndexTypes: Type[] = []; + for (const t of (objectType).types) { + if (isStringIndexOnlyType(t)) { + stringIndexTypes.push(getIndexTypeOfType(t, IndexKind.String)); + } + else { + regularTypes.push(t); + } } + return getUnionType([ + getIndexedAccessType(getIntersectionType(regularTypes), type.indexType), + getIntersectionType(stringIndexTypes) + ]); + } + // Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or + // more mapped types with a template type `never`, '(U & V & { [P in T]: never })[K]', return a + // transformed type that removes the never-mapped type: '(U & V)[K]'. This mirrors what would happen + // eventually anyway, but it easier to reason about. + if (some((objectType).types, isMappedTypeToNever)) { + const nonNeverTypes = filter((objectType).types, t => !isMappedTypeToNever(t)); + return getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType); } - return getUnionType([ - getIndexedAccessType(getIntersectionType(regularTypes), type.indexType), - getIntersectionType(stringIndexTypes) - ]); - } - // Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or - // more mapped types with a template type `never`, '(U & V & { [P in T]: never })[K]', return a - // transformed type that removes the never-mapped type: '(U & V)[K]'. This mirrors what would happen - // eventually anyway, but it easier to reason about. - if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType) && some((objectType).types, isMappedTypeToNever)) { - const nonNeverTypes = filter((objectType).types, t => !isMappedTypeToNever(t)); - return getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType); } // If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper From 428e0529fd81d9701e025ce98346b6b48e75dbd2 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 12 Jan 2018 13:54:49 -0800 Subject: [PATCH 080/172] Rename through all projects with the same file symLink --- src/compiler/sys.ts | 7 +- .../unittests/tsserverProjectSystem.ts | 35 +++--- src/harness/virtualFileSystemWithWatch.ts | 77 ++++++-------- src/server/editorServices.ts | 70 +++++++++--- src/server/scriptInfo.ts | 32 ++++++ src/server/session.ts | 100 ++++++++++++++---- .../reference/api/tsserverlibrary.d.ts | 6 +- 7 files changed, 226 insertions(+), 101 deletions(-) diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 841dba8a528..aa4bd61edce 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -524,7 +524,12 @@ namespace ts { process.exit(exitCode); }, realpath(path: string): string { - return _fs.realpathSync(path); + try { + return _fs.realpathSync(path); + } + catch { + return path; + } }, debugMode: some(process.execArgv, arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)), tryEnableSourceMapsForHost() { diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 2811584bf1b..e54988e223d 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -6604,6 +6604,7 @@ namespace ts.projectSystem { const host = createServerHost(files); const session = createSession(host); const projectService = session.getProjectService(); + debugger; session.executeCommandSeq({ command: protocol.CommandTypes.Open, arguments: { @@ -6637,6 +6638,7 @@ namespace ts.projectSystem { assert.isDefined(projectService.configuredProjects.get(aTsconfig.path)); assert.isDefined(projectService.configuredProjects.get(bTsconfig.path)); + debugger; verifyRenameResponse(session.executeCommandSeq({ command: protocol.CommandTypes.Rename, arguments: { @@ -6650,20 +6652,25 @@ namespace ts.projectSystem { function verifyRenameResponse({ info, locs }: protocol.RenameResponseBody) { assert.isTrue(info.canRename); - assert.equal(locs.length, 2); // Currently 2 but needs to be 4 - assert.deepEqual(locs[0], { - file: aFile.path, - locs: [ - { start: { line: 1, offset: 39 }, end: { line: 1, offset: 40 } }, - { start: { line: 1, offset: 9 }, end: { line: 1, offset: 10 } } - ] - }); - assert.deepEqual(locs[1], { - file: aFc, - locs: [ - { start: { line: 1, offset: 14 }, end: { line: 1, offset: 15 } } - ] - }); + assert.equal(locs.length, 4); + verifyLocations(0, aFile.path, aFc); + verifyLocations(2, bFile.path, bFc); + + function verifyLocations(locStartIndex: number, firstFile: string, secondFile: string) { + assert.deepEqual(locs[locStartIndex], { + file: firstFile, + locs: [ + { start: { line: 1, offset: 39 }, end: { line: 1, offset: 40 } }, + { start: { line: 1, offset: 9 }, end: { line: 1, offset: 10 } } + ] + }); + assert.deepEqual(locs[locStartIndex + 1], { + file: secondFile, + locs: [ + { start: { line: 1, offset: 14 }, end: { line: 1, offset: 15 } } + ] + }); + } } }); }); diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 5b326789247..90129e8e0f5 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -88,7 +88,7 @@ interface Array {}` } interface SymLink extends FSEntry { - symLink: Path; + symLink: string; } function isFolder(s: FSEntry): s is Folder { @@ -526,7 +526,7 @@ interface Array {}` return { path: this.toPath(fullPath), fullPath, - symLink: this.toPath(getNormalizedAbsolutePath(fileOrDirectory.symLink, getDirectoryPath(fullPath))) + symLink: getNormalizedAbsolutePath(fileOrDirectory.symLink, getDirectoryPath(fullPath)) }; } @@ -539,17 +539,13 @@ interface Array {}` }; } - private isFile(fsEntry: FSEntry) { - return !!this.getRealFile(fsEntry.path, fsEntry); - } - - private getRealFile(path: Path, fsEntry = this.fs.get(path)): File | undefined { - if (isFile(fsEntry)) { + private getRealFsEntry(isFsEntry: (fsEntry: FSEntry) => fsEntry is T, path: Path, fsEntry = this.fs.get(path)): T | undefined { + if (isFsEntry(fsEntry)) { return fsEntry; } if (isSymLink(fsEntry)) { - return this.getRealFile(fsEntry.symLink); + return this.getRealFsEntry(isFsEntry, this.toPath(fsEntry.symLink)); } if (fsEntry) { @@ -557,42 +553,28 @@ interface Array {}` return undefined; } - const dir = getDirectoryPath(path); - const dirEntry = this.getRealFolder(dir); - if (dirEntry && dirEntry.path !== dir) { - return this.getRealFile(combinePaths(dirEntry.path, getBaseFileName(path)) as Path); + const realpath = this.realpath(path); + if (path !== realpath) { + return this.getRealFsEntry(isFsEntry, realpath as Path); } + return undefined; } + private isFile(fsEntry: FSEntry) { + return !!this.getRealFile(fsEntry.path, fsEntry); + } + + private getRealFile(path: Path, fsEntry?: FSEntry): File | undefined { + return this.getRealFsEntry(isFile, path, fsEntry); + } + private isFolder(fsEntry: FSEntry) { return !!this.getRealFolder(fsEntry.path, fsEntry); } private getRealFolder(path: Path, fsEntry = this.fs.get(path)): Folder | undefined { - if (isFolder(fsEntry)) { - return fsEntry; - } - - if (isSymLink(fsEntry)) { - return this.getRealFolder(fsEntry.symLink); - } - - if (fsEntry) { - // This fs entry is something else - return undefined; - } - - const baseName = getBaseFileName(path); - const dir = getDirectoryPath(path); - if (dir !== baseName) { - const dirEntry = this.getRealFolder(dir); - if (dirEntry && dirEntry.path !== dir) { - return this.getRealFolder(combinePaths(dirEntry.path, baseName) as Path); - } - } - - return undefined; + return this.getRealFsEntry(isFolder, path, fsEntry); } fileExists(s: string) { @@ -765,16 +747,21 @@ interface Array {}` clear(this.output); } - realpath(s: string) { - while (true) { - const fsEntry = this.fs.get(this.toFullPath(s)); - if (isSymLink(fsEntry)) { - s = fsEntry.symLink; - } - else { - return s; - } + realpath(s: string): string { + const fullPath = this.toNormalizedAbsolutePath(s); + const path = this.toPath(fullPath); + if (getDirectoryPath(path) === path) { + // Root + return s; } + const dirFullPath = this.realpath(getDirectoryPath(fullPath)); + const realFullPath = combinePaths(dirFullPath, getBaseFileName(fullPath)); + const fsEntry = this.fs.get(this.toPath(realFullPath)); + if (isSymLink(fsEntry)) { + return this.realpath(fsEntry.symLink); + } + + return realFullPath; } readonly existMessage = "System Exit"; diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 664938a4cdc..864383d1052 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -198,16 +198,6 @@ namespace ts.server { } } - /** - * This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project. - */ - export function combineProjectOutput(projects: ReadonlyArray, action: (project: Project) => ReadonlyArray, comparer?: (a: T, b: T) => number, areEqual?: (a: T, b: T) => boolean) { - const outputs = flatMap(projects, action); - return comparer - ? sortAndDeduplicate(outputs, comparer, areEqual) - : deduplicate(outputs, areEqual); - } - export interface HostConfiguration { formatCodeOptions: FormatCodeSettings; hostInfo: string; @@ -335,6 +325,11 @@ namespace ts.server { * Container of all known scripts */ private readonly filenameToScriptInfo = createMap(); + /** + * Map to the real path of the infos + */ + /* @internal */ + readonly realpathToScriptInfos: MultiMap | undefined; /** * maps external project file name to list of config files that were the part of this project */ @@ -427,7 +422,9 @@ namespace ts.server { this.typesMapLocation = (opts.typesMapLocation === undefined) ? combinePaths(this.getExecutingFilePath(), "../typesMap.json") : opts.typesMapLocation; Debug.assert(!!this.host.createHash, "'ServerHost.createHash' is required for ProjectService"); - + if (this.host.realpath) { + this.realpathToScriptInfos = createMultiMap(); + } this.currentDirectory = this.host.getCurrentDirectory(); this.toCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames); this.throttledOperations = new ThrottledOperations(this.host, this.logger); @@ -768,7 +765,7 @@ namespace ts.server { if (info.containingProjects.length === 0) { // Orphan script info, remove it as we can always reload it on next open file request this.stopWatchingScriptInfo(info); - this.filenameToScriptInfo.delete(info.path); + this.deleteScriptInfo(info); } else { // file has been changed which might affect the set of referenced files in projects that include @@ -785,7 +782,7 @@ namespace ts.server { // TODO: handle isOpen = true case if (!info.isScriptOpen()) { - this.filenameToScriptInfo.delete(info.path); + this.deleteScriptInfo(info); // capture list of projects since detachAllProjects will wipe out original list const containingProjects = info.containingProjects.slice(); @@ -1019,11 +1016,19 @@ namespace ts.server { if (!info.isScriptOpen() && info.isOrphan()) { // if there are not projects that include this script info - delete it this.stopWatchingScriptInfo(info); - this.filenameToScriptInfo.delete(info.path); + this.deleteScriptInfo(info); } }); } + private deleteScriptInfo(info: ScriptInfo) { + this.filenameToScriptInfo.delete(info.path); + const realpath = info.getRealpathIfDifferent(); + if (realpath) { + this.realpathToScriptInfos.remove(realpath, info); + } + } + private configFileExists(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) { let configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath); if (configFileExistenceInfo) { @@ -1736,6 +1741,43 @@ namespace ts.server { return this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName)); } + /** + * Returns the projects that contain script info through SymLink + * Note that this does not return projects in info.containingProjects + */ + /*@internal*/ + getSymlinkedProjects(info: ScriptInfo): MultiMap | undefined { + let projects: MultiMap | undefined; + if (this.realpathToScriptInfos) { + const realpath = info.getRealpathIfDifferent(); + if (realpath) { + forEach(this.realpathToScriptInfos.get(realpath), combineProjects); + } + forEach(this.realpathToScriptInfos.get(info.path), combineProjects); + } + + return projects; + + function combineProjects(toAddInfo: ScriptInfo) { + if (toAddInfo !== info) { + for (const project of toAddInfo.containingProjects) { + // Add the projects only if they can use symLink targets and not already in the list + if (project.languageServiceEnabled && + !project.getCompilerOptions().preserveSymlinks && + !contains(info.containingProjects, project)) { + if (!projects) { + projects = createMultiMap(); + projects.add(toAddInfo.path, project); + } + else if (!forEachEntry(projects, (projs, path) => path === toAddInfo.path ? false : contains(projs, project))) { + projects.add(toAddInfo.path, project); + } + } + } + } + } + } + private watchClosedScriptInfo(info: ScriptInfo) { Debug.assert(!info.fileWatcher); // do not watch files with mixed content - server doesn't know how to interpret it diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index f800a1117d0..bf3cc05c0c8 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -213,6 +213,10 @@ namespace ts.server { /*@internal*/ readonly isDynamic: boolean; + /*@internal*/ + /** Set to real path if path is different from info.path */ + private realpath: Path | undefined; + constructor( private readonly host: ServerHost, readonly fileName: NormalizedPath, @@ -224,6 +228,7 @@ namespace ts.server { this.textStorage = new TextStorage(host, fileName); if (hasMixedContent || this.isDynamic) { this.textStorage.reload(""); + this.realpath = this.path; } this.scriptKind = scriptKind ? scriptKind @@ -264,6 +269,30 @@ namespace ts.server { return this.textStorage.getSnapshot(); } + private ensureRealPath() { + if (this.realpath === undefined) { + // Default is just the path + this.realpath = this.path; + if (this.host.realpath) { + Debug.assert(!!this.containingProjects.length); + const project = this.containingProjects[0]; + const realpath = this.host.realpath(this.path); + if (realpath) { + this.realpath = project.toPath(realpath); + // If it is different from this.path, add to the map + if (this.realpath !== this.path) { + project.projectService.realpathToScriptInfos.add(this.realpath, this); + } + } + } + } + } + + /*@internal*/ + getRealpathIfDifferent(): Path | undefined { + return this.realpath && this.realpath !== this.path ? this.realpath : undefined; + } + getFormatCodeSettings() { return this.formatCodeSettings; } @@ -272,6 +301,9 @@ namespace ts.server { const isNew = !this.isAttached(project); if (isNew) { this.containingProjects.push(project); + if (!project.getCompilerOptions().preserveSymlinks) { + this.ensureRealPath(); + } } return isNew; } diff --git a/src/server/session.ts b/src/server/session.ts index 693d1651c91..6b55b540577 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -255,6 +255,32 @@ namespace ts.server { }; } + type Projects = ReadonlyArray | { + projects: ReadonlyArray; + symLinkedProjects: MultiMap; + }; + + function isProjectsArray(projects: Projects): projects is ReadonlyArray { + return !!(>projects).length; + } + + /** + * This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project. + */ + function combineProjectOutput(defaultValue: T, getValue: (path: Path) => T, projects: Projects, action: (project: Project, value: T) => ReadonlyArray | U | undefined, comparer?: (a: U, b: U) => number, areEqual?: (a: U, b: U) => boolean) { + const outputs = flatMap(isProjectsArray(projects) ? projects : projects.projects, project => action(project, defaultValue)); + if (!isProjectsArray(projects) && projects.symLinkedProjects) { + projects.symLinkedProjects.forEach((projects, path) => { + const value = getValue(path as Path); + outputs.push(...flatMap(projects, project => action(project, value))); + }); + } + + return comparer + ? sortAndDeduplicate(outputs, comparer, areEqual) + : deduplicate(outputs, areEqual); + } + export interface SessionOptions { host: ServerHost; cancellationToken: ServerCancellationToken; @@ -789,8 +815,9 @@ namespace ts.server { return project.getLanguageService().getRenameInfo(file, position); } - private getProjects(args: protocol.FileRequestArgs) { - let projects: Project[]; + private getProjects(args: protocol.FileRequestArgs): Projects { + let projects: ReadonlyArray; + let symLinkedProjects: MultiMap | undefined; if (args.projectFileName) { const project = this.getProject(args.projectFileName); if (project) { @@ -800,13 +827,14 @@ namespace ts.server { else { const scriptInfo = this.projectService.getScriptInfo(args.file); projects = scriptInfo.containingProjects; + symLinkedProjects = this.projectService.getSymlinkedProjects(scriptInfo); } // filter handles case when 'projects' is undefined projects = filter(projects, p => p.languageServiceEnabled); - if (!projects || !projects.length) { + if ((!projects || !projects.length) && !symLinkedProjects) { return Errors.ThrowNoProject(); } - return projects; + return symLinkedProjects ? { projects, symLinkedProjects } : projects; } private getDefaultProject(args: protocol.FileRequestArgs) { @@ -841,8 +869,10 @@ namespace ts.server { } const fileSpans = combineProjectOutput( + file, + path => this.projectService.getScriptInfoForPath(path).fileName, projects, - (project: Project) => { + (project, file) => { const renameLocations = project.getLanguageService().findRenameLocations(file, position, args.findInStrings, args.findInComments); if (!renameLocations) { return emptyArray; @@ -881,8 +911,10 @@ namespace ts.server { } else { return combineProjectOutput( + file, + path => this.projectService.getScriptInfoForPath(path).fileName, projects, - p => p.getLanguageService().findRenameLocations(file, position, args.findInStrings, args.findInComments), + (p, file) => p.getLanguageService().findRenameLocations(file, position, args.findInStrings, args.findInComments), /*comparer*/ undefined, renameLocationIsEqualTo ); @@ -938,9 +970,11 @@ namespace ts.server { const nameSpan = nameInfo.textSpan; const nameColStart = scriptInfo.positionToLineOffset(nameSpan.start).offset; const nameText = scriptInfo.getSnapshot().getText(nameSpan.start, textSpanEnd(nameSpan)); - const refs = combineProjectOutput( + const refs = combineProjectOutput( + file, + path => this.projectService.getScriptInfoForPath(path).fileName, projects, - (project: Project) => { + (project, file) => { const references = project.getLanguageService().getReferencesAtPosition(file, position); if (!references) { return emptyArray; @@ -974,8 +1008,10 @@ namespace ts.server { } else { return combineProjectOutput( + file, + path => this.projectService.getScriptInfoForPath(path).fileName, projects, - project => project.getLanguageService().findReferences(file, position), + (project, file) => project.getLanguageService().findReferences(file, position), /*comparer*/ undefined, equateValues ); @@ -1240,20 +1276,25 @@ namespace ts.server { return emptyArray; } - const result: protocol.CompileOnSaveAffectedFileListSingleProject[] = []; - // if specified a project, we only return affected file list in this project - const projectsToSearch = args.projectFileName ? [this.projectService.findProject(args.projectFileName)] : info.containingProjects; - for (const project of projectsToSearch) { - if (project.compileOnSaveEnabled && project.languageServiceEnabled && !project.getCompilationSettings().noEmit) { - result.push({ - projectFileName: project.getProjectName(), - fileNames: project.getCompileOnSaveAffectedFileList(info), - projectUsesOutFile: !!project.getCompilationSettings().outFile || !!project.getCompilationSettings().out - }); + const projects = args.projectFileName ? [this.projectService.findProject(args.projectFileName)] : info.containingProjects; + const symLinkedProjects = !args.projectFileName && this.projectService.getSymlinkedProjects(info); + return combineProjectOutput( + info, + path => this.projectService.getScriptInfoForPath(path), + symLinkedProjects ? { projects, symLinkedProjects } : projects, + (project, info) => { + let result: protocol.CompileOnSaveAffectedFileListSingleProject; + if (project.compileOnSaveEnabled && project.languageServiceEnabled && !project.getCompilationSettings().noEmit) { + result = { + projectFileName: project.getProjectName(), + fileNames: project.getCompileOnSaveAffectedFileList(info), + projectUsesOutFile: !!project.getCompilationSettings().outFile || !!project.getCompilationSettings().out + }; + } + return result; } - } - return result; + ); } private emitFile(args: protocol.CompileOnSaveEmitFileRequestArgs) { @@ -1406,8 +1447,14 @@ namespace ts.server { const fileName = args.currentFileOnly ? args.file && normalizeSlashes(args.file) : undefined; if (simplifiedResult) { return combineProjectOutput( + fileName, + () => undefined, projects, - project => { + (project, file) => { + if (fileName && !file) { + return undefined; + } + const navItems = project.getLanguageService().getNavigateToItems(args.searchValue, args.maxResultCount, fileName, /*excludeDts*/ project.isNonTsProject()); if (!navItems) { return emptyArray; @@ -1443,8 +1490,15 @@ namespace ts.server { } else { return combineProjectOutput( + fileName, + () => undefined, projects, - project => project.getLanguageService().getNavigateToItems(args.searchValue, args.maxResultCount, fileName, /*excludeDts*/ project.isNonTsProject()), + (project, file) => { + if (fileName && !file) { + return undefined; + } + return project.getLanguageService().getNavigateToItems(args.searchValue, args.maxResultCount, fileName, /*excludeDts*/ project.isNonTsProject()); + }, /*comparer*/ undefined, navigateToItemIsEqualTo); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 3eabea4435a..ef26cbc743b 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7151,6 +7151,7 @@ declare namespace ts.server { open(newText: string): void; close(fileExists?: boolean): void; getSnapshot(): IScriptSnapshot; + private ensureRealPath(); getFormatCodeSettings(): FormatCodeSettings; attachToProject(project: Project): boolean; isAttached(project: Project): boolean; @@ -7523,10 +7524,6 @@ declare namespace ts.server { function convertCompilerOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): CompilerOptions & protocol.CompileOnSaveMixin; function tryConvertScriptKindName(scriptKindName: protocol.ScriptKindName | ScriptKind): ScriptKind; function convertScriptKindName(scriptKindName: protocol.ScriptKindName): ScriptKind.Unknown | ScriptKind.JS | ScriptKind.JSX | ScriptKind.TS | ScriptKind.TSX; - /** - * This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project. - */ - function combineProjectOutput(projects: ReadonlyArray, action: (project: Project) => ReadonlyArray, comparer?: (a: T, b: T) => number, areEqual?: (a: T, b: T) => boolean): T[]; interface HostConfiguration { formatCodeOptions: FormatCodeSettings; hostInfo: string; @@ -7662,6 +7659,7 @@ declare namespace ts.server { */ private closeOpenFile(info); private deleteOrphanScriptInfoNotInAnyProject(); + private deleteScriptInfo(info); private configFileExists(configFileName, canonicalConfigFilePath, info); private setConfigFileExistenceByNewConfiguredProject(project); /** From 64305edbcecf2e084e5fcc1534a8b1ee54f49a38 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 12 Jan 2018 18:24:02 -0800 Subject: [PATCH 081/172] Skip outer expressions when checking for super keyword in binder (#20164) * Skip outter expressions when checking for super keyword in binder * use TransformFlags to optimize and correct super call transforms * Lint --- src/compiler/binder.ts | 74 +++++++---- src/compiler/types.ts | 6 +- .../reference/decoratorOnClassMethod12.js | 1 - tests/baselines/reference/superAccess2.js | 1 - .../reference/superAccessCastedCall.js | 54 ++++++++ .../reference/superAccessCastedCall.symbols | 50 ++++++++ .../reference/superAccessCastedCall.types | 59 +++++++++ .../reference/superElementAccess.errors.txt | 44 +++++++ .../baselines/reference/superElementAccess.js | 84 +++++++++++++ .../reference/superElementAccess.symbols | 91 ++++++++++++++ .../reference/superElementAccess.types | 115 ++++++++++++++++++ tests/baselines/reference/superErrors.js | 12 +- tests/baselines/reference/superInLambdas.js | 2 - .../reference/superPropertyAccess.js | 5 +- ...opertyElementNoUnusedLexicalThisCapture.js | 53 ++++++++ ...yElementNoUnusedLexicalThisCapture.symbols | 33 +++++ ...rtyElementNoUnusedLexicalThisCapture.types | 39 ++++++ tests/cases/compiler/superAccessCastedCall.ts | 20 +++ tests/cases/compiler/superElementAccess.ts | 36 ++++++ ...opertyElementNoUnusedLexicalThisCapture.ts | 17 +++ 20 files changed, 753 insertions(+), 43 deletions(-) create mode 100644 tests/baselines/reference/superAccessCastedCall.js create mode 100644 tests/baselines/reference/superAccessCastedCall.symbols create mode 100644 tests/baselines/reference/superAccessCastedCall.types create mode 100644 tests/baselines/reference/superElementAccess.errors.txt create mode 100644 tests/baselines/reference/superElementAccess.js create mode 100644 tests/baselines/reference/superElementAccess.symbols create mode 100644 tests/baselines/reference/superElementAccess.types create mode 100644 tests/baselines/reference/superPropertyElementNoUnusedLexicalThisCapture.js create mode 100644 tests/baselines/reference/superPropertyElementNoUnusedLexicalThisCapture.symbols create mode 100644 tests/baselines/reference/superPropertyElementNoUnusedLexicalThisCapture.types create mode 100644 tests/cases/compiler/superAccessCastedCall.ts create mode 100644 tests/cases/compiler/superElementAccess.ts create mode 100644 tests/cases/compiler/superPropertyElementNoUnusedLexicalThisCapture.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 4e4b4f12705..d366ff3dfe5 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2740,6 +2740,9 @@ namespace ts { case SyntaxKind.PropertyAccessExpression: return computePropertyAccess(node, subtreeFlags); + case SyntaxKind.ElementAccessExpression: + return computeElementAccess(node, subtreeFlags); + default: return computeOther(node, kind, subtreeFlags); } @@ -2748,17 +2751,21 @@ namespace ts { function computeCallExpression(node: CallExpression, subtreeFlags: TransformFlags) { let transformFlags = subtreeFlags; const expression = node.expression; - const expressionKind = expression.kind; if (node.typeArguments) { transformFlags |= TransformFlags.AssertTypeScript; } if (subtreeFlags & TransformFlags.ContainsSpread - || isSuperOrSuperProperty(expression, expressionKind)) { + || (expression.transformFlags & (TransformFlags.Super | TransformFlags.ContainsSuper))) { // If the this node contains a SpreadExpression, or is a super call, then it is an ES6 // node. transformFlags |= TransformFlags.AssertES2015; + // super property or element accesses could be inside lambdas, etc, and need a captured `this`, + // while super keyword for super calls (indicated by TransformFlags.Super) does not (since it can only be top-level in a constructor) + if (expression.transformFlags & TransformFlags.ContainsSuper) { + transformFlags |= TransformFlags.ContainsLexicalThis; + } } if (expression.kind === SyntaxKind.ImportKeyword) { @@ -2775,21 +2782,6 @@ namespace ts { return transformFlags & ~TransformFlags.ArrayLiteralOrCallOrNewExcludes; } - function isSuperOrSuperProperty(node: Node, kind: SyntaxKind) { - switch (kind) { - case SyntaxKind.SuperKeyword: - return true; - - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - const expression = (node).expression; - const expressionKind = expression.kind; - return expressionKind === SyntaxKind.SuperKeyword; - } - - return false; - } - function computeNewExpression(node: NewExpression, subtreeFlags: TransformFlags) { let transformFlags = subtreeFlags; if (node.typeArguments) { @@ -2884,7 +2876,7 @@ namespace ts { } node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.NodeExcludes; + return transformFlags & ~TransformFlags.OuterExpressionExcludes; } function computeClassDeclaration(node: ClassDeclaration, subtreeFlags: TransformFlags) { @@ -3203,17 +3195,32 @@ namespace ts { function computePropertyAccess(node: PropertyAccessExpression, subtreeFlags: TransformFlags) { let transformFlags = subtreeFlags; - const expression = node.expression; - const expressionKind = expression.kind; // If a PropertyAccessExpression starts with a super keyword, then it is // ES6 syntax, and requires a lexical `this` binding. - if (expressionKind === SyntaxKind.SuperKeyword) { - transformFlags |= TransformFlags.ContainsLexicalThis; + if (transformFlags & TransformFlags.Super) { + transformFlags ^= TransformFlags.Super; + transformFlags |= TransformFlags.ContainsSuper; } node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.NodeExcludes; + return transformFlags & ~TransformFlags.PropertyAccessExcludes; + } + + function computeElementAccess(node: ElementAccessExpression, subtreeFlags: TransformFlags) { + let transformFlags = subtreeFlags; + const expression = node.expression; + const expressionFlags = expression.transformFlags; // We do not want to aggregate flags from the argument expression for super/this capturing + + // If an ElementAccessExpression starts with a super keyword, then it is + // ES6 syntax, and requires a lexical `this` binding. + if (expressionFlags & TransformFlags.Super) { + transformFlags &= ~TransformFlags.Super; + transformFlags |= TransformFlags.ContainsSuper; + } + + node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; + return transformFlags & ~TransformFlags.PropertyAccessExcludes; } function computeVariableDeclaration(node: VariableDeclaration, subtreeFlags: TransformFlags) { @@ -3333,6 +3340,13 @@ namespace ts { transformFlags |= TransformFlags.AssertESNext | TransformFlags.AssertES2017; break; + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + case SyntaxKind.PartiallyEmittedExpression: + // These nodes are TypeScript syntax. + transformFlags |= TransformFlags.AssertTypeScript; + excludeFlags = TransformFlags.OuterExpressionExcludes; + break; case SyntaxKind.PublicKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: @@ -3341,8 +3355,6 @@ namespace ts { case SyntaxKind.ConstKeyword: case SyntaxKind.EnumDeclaration: case SyntaxKind.EnumMember: - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.AsExpression: case SyntaxKind.NonNullExpression: case SyntaxKind.ReadonlyKeyword: // These nodes are TypeScript syntax. @@ -3470,7 +3482,8 @@ namespace ts { case SyntaxKind.SuperKeyword: // This node is ES6 syntax. - transformFlags |= TransformFlags.AssertES2015; + transformFlags |= TransformFlags.AssertES2015 | TransformFlags.Super; + excludeFlags = TransformFlags.OuterExpressionExcludes; // must be set to persist `Super` break; case SyntaxKind.ThisKeyword: @@ -3627,6 +3640,15 @@ namespace ts { case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ArrayBindingPattern: return TransformFlags.BindingPatternExcludes; + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + case SyntaxKind.PartiallyEmittedExpression: + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.SuperKeyword: + return TransformFlags.OuterExpressionExcludes; + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return TransformFlags.PropertyAccessExcludes; default: return TransformFlags.NodeExcludes; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e4ada136730..c51208c4f79 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4456,6 +4456,8 @@ namespace ts { ContainsYield = 1 << 24, ContainsHoistedDeclarationOrCompletion = 1 << 25, ContainsDynamicImport = 1 << 26, + Super = 1 << 27, + ContainsSuper = 1 << 28, // Please leave this as 1 << 29. // It is the maximum bit we can set before we outgrow the size of a v8 small integer (SMI) on an x86 system. @@ -4476,7 +4478,9 @@ namespace ts { // Scope Exclusions // - Bitmasks that exclude flags from propagating out of a specific context // into the subtree flags of their container. - NodeExcludes = TypeScript | ES2015 | DestructuringAssignment | Generator | HasComputedFlags, + OuterExpressionExcludes = TypeScript | ES2015 | DestructuringAssignment | Generator | HasComputedFlags, + PropertyAccessExcludes = OuterExpressionExcludes | Super, + NodeExcludes = PropertyAccessExcludes | ContainsSuper, ArrowFunctionExcludes = NodeExcludes | ContainsDecorators | ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsParameterPropertyAssignments | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRest, FunctionExcludes = NodeExcludes | ContainsDecorators | ContainsDefaultValueAssignments | ContainsCapturedLexicalThis | ContainsLexicalThis | ContainsParameterPropertyAssignments | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRest, ConstructorExcludes = NodeExcludes | ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRest, diff --git a/tests/baselines/reference/decoratorOnClassMethod12.js b/tests/baselines/reference/decoratorOnClassMethod12.js index 0063e0225bc..494cb083a20 100644 --- a/tests/baselines/reference/decoratorOnClassMethod12.js +++ b/tests/baselines/reference/decoratorOnClassMethod12.js @@ -28,7 +28,6 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, }; var M; (function (M) { - var _this = this; var S = /** @class */ (function () { function S() { } diff --git a/tests/baselines/reference/superAccess2.js b/tests/baselines/reference/superAccess2.js index d3aac217e22..de755361a16 100644 --- a/tests/baselines/reference/superAccess2.js +++ b/tests/baselines/reference/superAccess2.js @@ -35,7 +35,6 @@ var __extends = (this && this.__extends) || (function () { d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); -var _this = this; var P = /** @class */ (function () { function P() { } diff --git a/tests/baselines/reference/superAccessCastedCall.js b/tests/baselines/reference/superAccessCastedCall.js new file mode 100644 index 00000000000..a4583b3e710 --- /dev/null +++ b/tests/baselines/reference/superAccessCastedCall.js @@ -0,0 +1,54 @@ +//// [superAccessCastedCall.ts] +class Foo { + bar(): void {} +} + +class Bar extends Foo { + x: Number; + + constructor() { + super(); + this.x = 2; + } + + bar() { + super.bar(); + (super.bar as any)(); + } +} + +let b = new Bar(); +b.bar() + +//// [superAccessCastedCall.js] +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var Foo = /** @class */ (function () { + function Foo() { + } + Foo.prototype.bar = function () { }; + return Foo; +}()); +var Bar = /** @class */ (function (_super) { + __extends(Bar, _super); + function Bar() { + var _this = _super.call(this) || this; + _this.x = 2; + return _this; + } + Bar.prototype.bar = function () { + _super.prototype.bar.call(this); + _super.prototype.bar.call(this); + }; + return Bar; +}(Foo)); +var b = new Bar(); +b.bar(); diff --git a/tests/baselines/reference/superAccessCastedCall.symbols b/tests/baselines/reference/superAccessCastedCall.symbols new file mode 100644 index 00000000000..83b9447c3f7 --- /dev/null +++ b/tests/baselines/reference/superAccessCastedCall.symbols @@ -0,0 +1,50 @@ +=== tests/cases/compiler/superAccessCastedCall.ts === +class Foo { +>Foo : Symbol(Foo, Decl(superAccessCastedCall.ts, 0, 0)) + + bar(): void {} +>bar : Symbol(Foo.bar, Decl(superAccessCastedCall.ts, 0, 11)) +} + +class Bar extends Foo { +>Bar : Symbol(Bar, Decl(superAccessCastedCall.ts, 2, 1)) +>Foo : Symbol(Foo, Decl(superAccessCastedCall.ts, 0, 0)) + + x: Number; +>x : Symbol(Bar.x, Decl(superAccessCastedCall.ts, 4, 23)) +>Number : Symbol(Number, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + + constructor() { + super(); +>super : Symbol(Foo, Decl(superAccessCastedCall.ts, 0, 0)) + + this.x = 2; +>this.x : Symbol(Bar.x, Decl(superAccessCastedCall.ts, 4, 23)) +>this : Symbol(Bar, Decl(superAccessCastedCall.ts, 2, 1)) +>x : Symbol(Bar.x, Decl(superAccessCastedCall.ts, 4, 23)) + } + + bar() { +>bar : Symbol(Bar.bar, Decl(superAccessCastedCall.ts, 10, 5)) + + super.bar(); +>super.bar : Symbol(Foo.bar, Decl(superAccessCastedCall.ts, 0, 11)) +>super : Symbol(Foo, Decl(superAccessCastedCall.ts, 0, 0)) +>bar : Symbol(Foo.bar, Decl(superAccessCastedCall.ts, 0, 11)) + + (super.bar as any)(); +>super.bar : Symbol(Foo.bar, Decl(superAccessCastedCall.ts, 0, 11)) +>super : Symbol(Foo, Decl(superAccessCastedCall.ts, 0, 0)) +>bar : Symbol(Foo.bar, Decl(superAccessCastedCall.ts, 0, 11)) + } +} + +let b = new Bar(); +>b : Symbol(b, Decl(superAccessCastedCall.ts, 18, 3)) +>Bar : Symbol(Bar, Decl(superAccessCastedCall.ts, 2, 1)) + +b.bar() +>b.bar : Symbol(Bar.bar, Decl(superAccessCastedCall.ts, 10, 5)) +>b : Symbol(b, Decl(superAccessCastedCall.ts, 18, 3)) +>bar : Symbol(Bar.bar, Decl(superAccessCastedCall.ts, 10, 5)) + diff --git a/tests/baselines/reference/superAccessCastedCall.types b/tests/baselines/reference/superAccessCastedCall.types new file mode 100644 index 00000000000..72ee3c05a7e --- /dev/null +++ b/tests/baselines/reference/superAccessCastedCall.types @@ -0,0 +1,59 @@ +=== tests/cases/compiler/superAccessCastedCall.ts === +class Foo { +>Foo : Foo + + bar(): void {} +>bar : () => void +} + +class Bar extends Foo { +>Bar : Bar +>Foo : Foo + + x: Number; +>x : Number +>Number : Number + + constructor() { + super(); +>super() : void +>super : typeof Foo + + this.x = 2; +>this.x = 2 : 2 +>this.x : Number +>this : this +>x : Number +>2 : 2 + } + + bar() { +>bar : () => void + + super.bar(); +>super.bar() : void +>super.bar : () => void +>super : Foo +>bar : () => void + + (super.bar as any)(); +>(super.bar as any)() : any +>(super.bar as any) : any +>super.bar as any : any +>super.bar : () => void +>super : Foo +>bar : () => void + } +} + +let b = new Bar(); +>b : Bar +>new Bar() : Bar +>Bar : typeof Bar + +b.bar() +>b.bar() : void +>b.bar : () => void +>b : Bar +>bar : () => void + diff --git a/tests/baselines/reference/superElementAccess.errors.txt b/tests/baselines/reference/superElementAccess.errors.txt new file mode 100644 index 00000000000..e5d8c3ea5ad --- /dev/null +++ b/tests/baselines/reference/superElementAccess.errors.txt @@ -0,0 +1,44 @@ +tests/cases/compiler/superElementAccess.ts(7,9): error TS1056: Accessors are only available when targeting ECMAScript 5 and higher. +tests/cases/compiler/superElementAccess.ts(8,9): error TS1056: Accessors are only available when targeting ECMAScript 5 and higher. + + +==== tests/cases/compiler/superElementAccess.ts (2 errors) ==== + class MyBase { + m1(a: string) { return a; } + private p1() { } + m2: () => void = function () { } + d1: number = 42; + private d2: number = 42; + get value() {return 0 } + ~~~~~ +!!! error TS1056: Accessors are only available when targeting ECMAScript 5 and higher. + set value(v: number) { } + ~~~~~ +!!! error TS1056: Accessors are only available when targeting ECMAScript 5 and higher. + } + + + class MyDerived extends MyBase { + + foo() { + super["m1"]("hi"); // Should be allowed, method on base prototype + + var l2 = super["m1"].bind(this); // Should be allowed, can access properties as well as invoke + + var x: (a: string) => string = super["m1"]; // Should be allowed, can assign to var with compatible signature + + super["m2"].bind(this); // Should error, instance property, not a public instance member function + + super["p1"](); // Should error, private not public instance member function + + var l1 = super["d1"]; // Should error, instance data property not a public instance member function + + var l1 = super["d2"]; // Should error, instance data property not a public instance member function + + super["m1"] = function (a: string) { return ""; }; // Should be allowed, we will not restrict assignment + + super["value"] = 0; // Should error, instance data property not a public instance member function + + var z = super["value"]; // Should error, instance data property not a public instance member function + } + } \ No newline at end of file diff --git a/tests/baselines/reference/superElementAccess.js b/tests/baselines/reference/superElementAccess.js new file mode 100644 index 00000000000..422b80109bb --- /dev/null +++ b/tests/baselines/reference/superElementAccess.js @@ -0,0 +1,84 @@ +//// [superElementAccess.ts] +class MyBase { + m1(a: string) { return a; } + private p1() { } + m2: () => void = function () { } + d1: number = 42; + private d2: number = 42; + get value() {return 0 } + set value(v: number) { } +} + + +class MyDerived extends MyBase { + + foo() { + super["m1"]("hi"); // Should be allowed, method on base prototype + + var l2 = super["m1"].bind(this); // Should be allowed, can access properties as well as invoke + + var x: (a: string) => string = super["m1"]; // Should be allowed, can assign to var with compatible signature + + super["m2"].bind(this); // Should error, instance property, not a public instance member function + + super["p1"](); // Should error, private not public instance member function + + var l1 = super["d1"]; // Should error, instance data property not a public instance member function + + var l1 = super["d2"]; // Should error, instance data property not a public instance member function + + super["m1"] = function (a: string) { return ""; }; // Should be allowed, we will not restrict assignment + + super["value"] = 0; // Should error, instance data property not a public instance member function + + var z = super["value"]; // Should error, instance data property not a public instance member function + } +} + +//// [superElementAccess.js] +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var MyBase = /** @class */ (function () { + function MyBase() { + this.m2 = function () { }; + this.d1 = 42; + this.d2 = 42; + } + MyBase.prototype.m1 = function (a) { return a; }; + MyBase.prototype.p1 = function () { }; + Object.defineProperty(MyBase.prototype, "value", { + get: function () { return 0; }, + set: function (v) { }, + enumerable: true, + configurable: true + }); + return MyBase; +}()); +var MyDerived = /** @class */ (function (_super) { + __extends(MyDerived, _super); + function MyDerived() { + return _super !== null && _super.apply(this, arguments) || this; + } + MyDerived.prototype.foo = function () { + _super.prototype["m1"].call(this, "hi"); // Should be allowed, method on base prototype + var l2 = (_a = _super.prototype["m1"]).bind.call(_a, this); // Should be allowed, can access properties as well as invoke + var x = _super.prototype["m1"]; // Should be allowed, can assign to var with compatible signature + (_b = _super.prototype["m2"]).bind.call(_b, this); // Should error, instance property, not a public instance member function + _super.prototype["p1"].call(this); // Should error, private not public instance member function + var l1 = _super.prototype["d1"]; // Should error, instance data property not a public instance member function + var l1 = _super.prototype["d2"]; // Should error, instance data property not a public instance member function + _super.prototype["m1"] = function (a) { return ""; }; // Should be allowed, we will not restrict assignment + _super.prototype["value"] = 0; // Should error, instance data property not a public instance member function + var z = _super.prototype["value"]; // Should error, instance data property not a public instance member function + var _a, _b; + }; + return MyDerived; +}(MyBase)); diff --git a/tests/baselines/reference/superElementAccess.symbols b/tests/baselines/reference/superElementAccess.symbols new file mode 100644 index 00000000000..5da1450212b --- /dev/null +++ b/tests/baselines/reference/superElementAccess.symbols @@ -0,0 +1,91 @@ +=== tests/cases/compiler/superElementAccess.ts === +class MyBase { +>MyBase : Symbol(MyBase, Decl(superElementAccess.ts, 0, 0)) + + m1(a: string) { return a; } +>m1 : Symbol(MyBase.m1, Decl(superElementAccess.ts, 0, 14)) +>a : Symbol(a, Decl(superElementAccess.ts, 1, 7)) +>a : Symbol(a, Decl(superElementAccess.ts, 1, 7)) + + private p1() { } +>p1 : Symbol(MyBase.p1, Decl(superElementAccess.ts, 1, 31)) + + m2: () => void = function () { } +>m2 : Symbol(MyBase.m2, Decl(superElementAccess.ts, 2, 20)) + + d1: number = 42; +>d1 : Symbol(MyBase.d1, Decl(superElementAccess.ts, 3, 36)) + + private d2: number = 42; +>d2 : Symbol(MyBase.d2, Decl(superElementAccess.ts, 4, 20)) + + get value() {return 0 } +>value : Symbol(MyBase.value, Decl(superElementAccess.ts, 5, 28), Decl(superElementAccess.ts, 6, 27)) + + set value(v: number) { } +>value : Symbol(MyBase.value, Decl(superElementAccess.ts, 5, 28), Decl(superElementAccess.ts, 6, 27)) +>v : Symbol(v, Decl(superElementAccess.ts, 7, 14)) +} + + +class MyDerived extends MyBase { +>MyDerived : Symbol(MyDerived, Decl(superElementAccess.ts, 8, 1)) +>MyBase : Symbol(MyBase, Decl(superElementAccess.ts, 0, 0)) + + foo() { +>foo : Symbol(MyDerived.foo, Decl(superElementAccess.ts, 11, 32)) + + super["m1"]("hi"); // Should be allowed, method on base prototype +>super : Symbol(MyBase, Decl(superElementAccess.ts, 0, 0)) +>"m1" : Symbol(MyBase.m1, Decl(superElementAccess.ts, 0, 14)) + + var l2 = super["m1"].bind(this); // Should be allowed, can access properties as well as invoke +>l2 : Symbol(l2, Decl(superElementAccess.ts, 16, 11)) +>super["m1"].bind : Symbol(Function.bind, Decl(lib.d.ts, --, --)) +>super : Symbol(MyBase, Decl(superElementAccess.ts, 0, 0)) +>"m1" : Symbol(MyBase.m1, Decl(superElementAccess.ts, 0, 14)) +>bind : Symbol(Function.bind, Decl(lib.d.ts, --, --)) +>this : Symbol(MyDerived, Decl(superElementAccess.ts, 8, 1)) + + var x: (a: string) => string = super["m1"]; // Should be allowed, can assign to var with compatible signature +>x : Symbol(x, Decl(superElementAccess.ts, 18, 11)) +>a : Symbol(a, Decl(superElementAccess.ts, 18, 16)) +>super : Symbol(MyBase, Decl(superElementAccess.ts, 0, 0)) +>"m1" : Symbol(MyBase.m1, Decl(superElementAccess.ts, 0, 14)) + + super["m2"].bind(this); // Should error, instance property, not a public instance member function +>super["m2"].bind : Symbol(Function.bind, Decl(lib.d.ts, --, --)) +>super : Symbol(MyBase, Decl(superElementAccess.ts, 0, 0)) +>"m2" : Symbol(MyBase.m2, Decl(superElementAccess.ts, 2, 20)) +>bind : Symbol(Function.bind, Decl(lib.d.ts, --, --)) +>this : Symbol(MyDerived, Decl(superElementAccess.ts, 8, 1)) + + super["p1"](); // Should error, private not public instance member function +>super : Symbol(MyBase, Decl(superElementAccess.ts, 0, 0)) +>"p1" : Symbol(MyBase.p1, Decl(superElementAccess.ts, 1, 31)) + + var l1 = super["d1"]; // Should error, instance data property not a public instance member function +>l1 : Symbol(l1, Decl(superElementAccess.ts, 24, 11), Decl(superElementAccess.ts, 26, 11)) +>super : Symbol(MyBase, Decl(superElementAccess.ts, 0, 0)) +>"d1" : Symbol(MyBase.d1, Decl(superElementAccess.ts, 3, 36)) + + var l1 = super["d2"]; // Should error, instance data property not a public instance member function +>l1 : Symbol(l1, Decl(superElementAccess.ts, 24, 11), Decl(superElementAccess.ts, 26, 11)) +>super : Symbol(MyBase, Decl(superElementAccess.ts, 0, 0)) +>"d2" : Symbol(MyBase.d2, Decl(superElementAccess.ts, 4, 20)) + + super["m1"] = function (a: string) { return ""; }; // Should be allowed, we will not restrict assignment +>super : Symbol(MyBase, Decl(superElementAccess.ts, 0, 0)) +>"m1" : Symbol(MyBase.m1, Decl(superElementAccess.ts, 0, 14)) +>a : Symbol(a, Decl(superElementAccess.ts, 28, 32)) + + super["value"] = 0; // Should error, instance data property not a public instance member function +>super : Symbol(MyBase, Decl(superElementAccess.ts, 0, 0)) +>"value" : Symbol(MyBase.value, Decl(superElementAccess.ts, 5, 28), Decl(superElementAccess.ts, 6, 27)) + + var z = super["value"]; // Should error, instance data property not a public instance member function +>z : Symbol(z, Decl(superElementAccess.ts, 32, 11)) +>super : Symbol(MyBase, Decl(superElementAccess.ts, 0, 0)) +>"value" : Symbol(MyBase.value, Decl(superElementAccess.ts, 5, 28), Decl(superElementAccess.ts, 6, 27)) + } +} diff --git a/tests/baselines/reference/superElementAccess.types b/tests/baselines/reference/superElementAccess.types new file mode 100644 index 00000000000..9fa75ec6bc4 --- /dev/null +++ b/tests/baselines/reference/superElementAccess.types @@ -0,0 +1,115 @@ +=== tests/cases/compiler/superElementAccess.ts === +class MyBase { +>MyBase : MyBase + + m1(a: string) { return a; } +>m1 : (a: string) => string +>a : string +>a : string + + private p1() { } +>p1 : () => void + + m2: () => void = function () { } +>m2 : () => void +>function () { } : () => void + + d1: number = 42; +>d1 : number +>42 : 42 + + private d2: number = 42; +>d2 : number +>42 : 42 + + get value() {return 0 } +>value : number +>0 : 0 + + set value(v: number) { } +>value : number +>v : number +} + + +class MyDerived extends MyBase { +>MyDerived : MyDerived +>MyBase : MyBase + + foo() { +>foo : () => void + + super["m1"]("hi"); // Should be allowed, method on base prototype +>super["m1"]("hi") : string +>super["m1"] : (a: string) => string +>super : MyBase +>"m1" : "m1" +>"hi" : "hi" + + var l2 = super["m1"].bind(this); // Should be allowed, can access properties as well as invoke +>l2 : any +>super["m1"].bind(this) : any +>super["m1"].bind : (this: Function, thisArg: any, ...argArray: any[]) => any +>super["m1"] : (a: string) => string +>super : MyBase +>"m1" : "m1" +>bind : (this: Function, thisArg: any, ...argArray: any[]) => any +>this : this + + var x: (a: string) => string = super["m1"]; // Should be allowed, can assign to var with compatible signature +>x : (a: string) => string +>a : string +>super["m1"] : (a: string) => string +>super : MyBase +>"m1" : "m1" + + super["m2"].bind(this); // Should error, instance property, not a public instance member function +>super["m2"].bind(this) : any +>super["m2"].bind : (this: Function, thisArg: any, ...argArray: any[]) => any +>super["m2"] : () => void +>super : MyBase +>"m2" : "m2" +>bind : (this: Function, thisArg: any, ...argArray: any[]) => any +>this : this + + super["p1"](); // Should error, private not public instance member function +>super["p1"]() : void +>super["p1"] : () => void +>super : MyBase +>"p1" : "p1" + + var l1 = super["d1"]; // Should error, instance data property not a public instance member function +>l1 : number +>super["d1"] : number +>super : MyBase +>"d1" : "d1" + + var l1 = super["d2"]; // Should error, instance data property not a public instance member function +>l1 : number +>super["d2"] : number +>super : MyBase +>"d2" : "d2" + + super["m1"] = function (a: string) { return ""; }; // Should be allowed, we will not restrict assignment +>super["m1"] = function (a: string) { return ""; } : (a: string) => string +>super["m1"] : (a: string) => string +>super : MyBase +>"m1" : "m1" +>function (a: string) { return ""; } : (a: string) => string +>a : string +>"" : "" + + super["value"] = 0; // Should error, instance data property not a public instance member function +>super["value"] = 0 : 0 +>super["value"] : number +>super : MyBase +>"value" : "value" +>0 : 0 + + var z = super["value"]; // Should error, instance data property not a public instance member function +>z : number +>super["value"] : number +>super : MyBase +>"value" : "value" + } +} diff --git a/tests/baselines/reference/superErrors.js b/tests/baselines/reference/superErrors.js index 87d4e3bb85a..952cb03fd80 100644 --- a/tests/baselines/reference/superErrors.js +++ b/tests/baselines/reference/superErrors.js @@ -63,7 +63,6 @@ var __extends = (this && this.__extends) || (function () { }; })(); function foo() { - var _this = this; // super in a non class context var x = _super.; var y = function () { return _super.; }; @@ -93,10 +92,7 @@ var RegisteredUser = /** @class */ (function (_super) { var x = function () { return _super.sayHello.call(_this); }; } // super call in a lambda in a function expression in a constructor - (function () { - var _this = this; - return function () { return _super.; }; - })(); + (function () { return function () { return _super.; }; })(); return _this; } RegisteredUser.prototype.sayHello = function () { @@ -108,13 +104,9 @@ var RegisteredUser = /** @class */ (function (_super) { var x = function () { return _super.sayHello.call(_this); }; } // super call in a lambda in a function expression in a constructor - (function () { - var _this = this; - return function () { return _super.; }; - })(); + (function () { return function () { return _super.; }; })(); }; RegisteredUser.staticFunction = function () { - var _this = this; // super in static functions var s = _super.; var x = function () { return _super.; }; diff --git a/tests/baselines/reference/superInLambdas.js b/tests/baselines/reference/superInLambdas.js index 653d3ae84c1..9bb1a87e0ea 100644 --- a/tests/baselines/reference/superInLambdas.js +++ b/tests/baselines/reference/superInLambdas.js @@ -133,7 +133,6 @@ var RegisteredUser3 = /** @class */ (function (_super) { return _this; } RegisteredUser3.prototype.sayHello = function () { - var _this = this; // super property in a nested lambda in a method var superName = function () { return function () { return function () { return _super.prototype.name; }; }; }; }; @@ -149,7 +148,6 @@ var RegisteredUser4 = /** @class */ (function (_super) { return _this; } RegisteredUser4.prototype.sayHello = function () { - var _this = this; // super in a nested lambda in a method var x = function () { return function () { return _super.prototype.; }; }; }; diff --git a/tests/baselines/reference/superPropertyAccess.js b/tests/baselines/reference/superPropertyAccess.js index cc9758a1617..8fa0984745c 100644 --- a/tests/baselines/reference/superPropertyAccess.js +++ b/tests/baselines/reference/superPropertyAccess.js @@ -69,15 +69,16 @@ var MyDerived = /** @class */ (function (_super) { } MyDerived.prototype.foo = function () { _super.prototype.m1.call(this, "hi"); // Should be allowed, method on base prototype - var l2 = _super.prototype.m1.bind(this); // Should be allowed, can access properties as well as invoke + var l2 = (_a = _super.prototype.m1).bind.call(_a, this); // Should be allowed, can access properties as well as invoke var x = _super.prototype.m1; // Should be allowed, can assign to var with compatible signature - _super.prototype.m2.bind(this); // Should error, instance property, not a public instance member function + (_b = _super.prototype.m2).bind.call(_b, this); // Should error, instance property, not a public instance member function _super.prototype.p1.call(this); // Should error, private not public instance member function var l1 = _super.prototype.d1; // Should error, instance data property not a public instance member function var l1 = _super.prototype.d2; // Should error, instance data property not a public instance member function _super.prototype.m1 = function (a) { return ""; }; // Should be allowed, we will not restrict assignment _super.prototype.value = 0; // Should error, instance data property not a public instance member function var z = _super.prototype.value; // Should error, instance data property not a public instance member function + var _a, _b; }; return MyDerived; }(MyBase)); diff --git a/tests/baselines/reference/superPropertyElementNoUnusedLexicalThisCapture.js b/tests/baselines/reference/superPropertyElementNoUnusedLexicalThisCapture.js new file mode 100644 index 00000000000..126c123b40d --- /dev/null +++ b/tests/baselines/reference/superPropertyElementNoUnusedLexicalThisCapture.js @@ -0,0 +1,53 @@ +//// [superPropertyElementNoUnusedLexicalThisCapture.ts] +class A { x() {} } + +class B extends A { + constructor() { + super(); + } + foo() { + return () => { + super.x; + } + } + bar() { + return () => { + super["x"]; + } + } +} + +//// [superPropertyElementNoUnusedLexicalThisCapture.js] +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var A = /** @class */ (function () { + function A() { + } + A.prototype.x = function () { }; + return A; +}()); +var B = /** @class */ (function (_super) { + __extends(B, _super); + function B() { + return _super.call(this) || this; + } + B.prototype.foo = function () { + return function () { + _super.prototype.x; + }; + }; + B.prototype.bar = function () { + return function () { + _super.prototype["x"]; + }; + }; + return B; +}(A)); diff --git a/tests/baselines/reference/superPropertyElementNoUnusedLexicalThisCapture.symbols b/tests/baselines/reference/superPropertyElementNoUnusedLexicalThisCapture.symbols new file mode 100644 index 00000000000..080268a0744 --- /dev/null +++ b/tests/baselines/reference/superPropertyElementNoUnusedLexicalThisCapture.symbols @@ -0,0 +1,33 @@ +=== tests/cases/compiler/superPropertyElementNoUnusedLexicalThisCapture.ts === +class A { x() {} } +>A : Symbol(A, Decl(superPropertyElementNoUnusedLexicalThisCapture.ts, 0, 0)) +>x : Symbol(A.x, Decl(superPropertyElementNoUnusedLexicalThisCapture.ts, 0, 9)) + +class B extends A { +>B : Symbol(B, Decl(superPropertyElementNoUnusedLexicalThisCapture.ts, 0, 18)) +>A : Symbol(A, Decl(superPropertyElementNoUnusedLexicalThisCapture.ts, 0, 0)) + + constructor() { + super(); +>super : Symbol(A, Decl(superPropertyElementNoUnusedLexicalThisCapture.ts, 0, 0)) + } + foo() { +>foo : Symbol(B.foo, Decl(superPropertyElementNoUnusedLexicalThisCapture.ts, 5, 5)) + + return () => { + super.x; +>super.x : Symbol(A.x, Decl(superPropertyElementNoUnusedLexicalThisCapture.ts, 0, 9)) +>super : Symbol(A, Decl(superPropertyElementNoUnusedLexicalThisCapture.ts, 0, 0)) +>x : Symbol(A.x, Decl(superPropertyElementNoUnusedLexicalThisCapture.ts, 0, 9)) + } + } + bar() { +>bar : Symbol(B.bar, Decl(superPropertyElementNoUnusedLexicalThisCapture.ts, 10, 5)) + + return () => { + super["x"]; +>super : Symbol(A, Decl(superPropertyElementNoUnusedLexicalThisCapture.ts, 0, 0)) +>"x" : Symbol(A.x, Decl(superPropertyElementNoUnusedLexicalThisCapture.ts, 0, 9)) + } + } +} diff --git a/tests/baselines/reference/superPropertyElementNoUnusedLexicalThisCapture.types b/tests/baselines/reference/superPropertyElementNoUnusedLexicalThisCapture.types new file mode 100644 index 00000000000..37048f2f1d5 --- /dev/null +++ b/tests/baselines/reference/superPropertyElementNoUnusedLexicalThisCapture.types @@ -0,0 +1,39 @@ +=== tests/cases/compiler/superPropertyElementNoUnusedLexicalThisCapture.ts === +class A { x() {} } +>A : A +>x : () => void + +class B extends A { +>B : B +>A : A + + constructor() { + super(); +>super() : void +>super : typeof A + } + foo() { +>foo : () => () => void + + return () => { +>() => { super.x; } : () => void + + super.x; +>super.x : () => void +>super : A +>x : () => void + } + } + bar() { +>bar : () => () => void + + return () => { +>() => { super["x"]; } : () => void + + super["x"]; +>super["x"] : () => void +>super : A +>"x" : "x" + } + } +} diff --git a/tests/cases/compiler/superAccessCastedCall.ts b/tests/cases/compiler/superAccessCastedCall.ts new file mode 100644 index 00000000000..98f154834e1 --- /dev/null +++ b/tests/cases/compiler/superAccessCastedCall.ts @@ -0,0 +1,20 @@ +class Foo { + bar(): void {} +} + +class Bar extends Foo { + x: Number; + + constructor() { + super(); + this.x = 2; + } + + bar() { + super.bar(); + (super.bar as any)(); + } +} + +let b = new Bar(); +b.bar() \ No newline at end of file diff --git a/tests/cases/compiler/superElementAccess.ts b/tests/cases/compiler/superElementAccess.ts new file mode 100644 index 00000000000..1b7e3f45683 --- /dev/null +++ b/tests/cases/compiler/superElementAccess.ts @@ -0,0 +1,36 @@ + +class MyBase { + m1(a: string) { return a; } + private p1() { } + m2: () => void = function () { } + d1: number = 42; + private d2: number = 42; + get value() {return 0 } + set value(v: number) { } +} + + +class MyDerived extends MyBase { + + foo() { + super["m1"]("hi"); // Should be allowed, method on base prototype + + var l2 = super["m1"].bind(this); // Should be allowed, can access properties as well as invoke + + var x: (a: string) => string = super["m1"]; // Should be allowed, can assign to var with compatible signature + + super["m2"].bind(this); // Should error, instance property, not a public instance member function + + super["p1"](); // Should error, private not public instance member function + + var l1 = super["d1"]; // Should error, instance data property not a public instance member function + + var l1 = super["d2"]; // Should error, instance data property not a public instance member function + + super["m1"] = function (a: string) { return ""; }; // Should be allowed, we will not restrict assignment + + super["value"] = 0; // Should error, instance data property not a public instance member function + + var z = super["value"]; // Should error, instance data property not a public instance member function + } +} \ No newline at end of file diff --git a/tests/cases/compiler/superPropertyElementNoUnusedLexicalThisCapture.ts b/tests/cases/compiler/superPropertyElementNoUnusedLexicalThisCapture.ts new file mode 100644 index 00000000000..b5e40b7ceff --- /dev/null +++ b/tests/cases/compiler/superPropertyElementNoUnusedLexicalThisCapture.ts @@ -0,0 +1,17 @@ +class A { x() {} } + +class B extends A { + constructor() { + super(); + } + foo() { + return () => { + super.x; + } + } + bar() { + return () => { + super["x"]; + } + } +} \ No newline at end of file From 2e1738bffaa9af5feaf343830f892c84abd82ced Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 12 Jan 2018 18:24:41 -0800 Subject: [PATCH 082/172] Enable substitution for object literal shorthand property assignments in the system transform (#21106) --- src/compiler/transformers/module/system.ts | 55 +++++++++++++++++++ .../reference/systemObjectShorthandRename.js | 44 +++++++++++++++ .../systemObjectShorthandRename.symbols | 24 ++++++++ .../systemObjectShorthandRename.types | 28 ++++++++++ .../compiler/systemObjectShorthandRename.ts | 12 ++++ 5 files changed, 163 insertions(+) create mode 100644 tests/baselines/reference/systemObjectShorthandRename.js create mode 100644 tests/baselines/reference/systemObjectShorthandRename.symbols create mode 100644 tests/baselines/reference/systemObjectShorthandRename.types create mode 100644 tests/cases/compiler/systemObjectShorthandRename.ts diff --git a/src/compiler/transformers/module/system.ts b/src/compiler/transformers/module/system.ts index e5d638cf04d..2e6f6d84b51 100644 --- a/src/compiler/transformers/module/system.ts +++ b/src/compiler/transformers/module/system.ts @@ -24,6 +24,7 @@ namespace ts { context.onSubstituteNode = onSubstituteNode; context.onEmitNode = onEmitNode; context.enableSubstitution(SyntaxKind.Identifier); // Substitutes expression identifiers for imported symbols. + context.enableSubstitution(SyntaxKind.ShorthandPropertyAssignment); // Substitutes expression identifiers for imported symbols context.enableSubstitution(SyntaxKind.BinaryExpression); // Substitutes assignments to exported symbols. context.enableSubstitution(SyntaxKind.PrefixUnaryExpression); // Substitutes updates to exported symbols. context.enableSubstitution(SyntaxKind.PostfixUnaryExpression); // Substitutes updates to exported symbols. @@ -1625,10 +1626,64 @@ namespace ts { if (hint === EmitHint.Expression) { return substituteExpression(node); } + else if (hint === EmitHint.Unspecified) { + return substituteUnspecified(node); + } return node; } + /** + * Substitute the node, if necessary. + * + * @param node The node to substitute. + */ + function substituteUnspecified(node: Node) { + switch (node.kind) { + case SyntaxKind.ShorthandPropertyAssignment: + return substituteShorthandPropertyAssignment(node); + } + return node; + } + /** + * Substitution for a ShorthandPropertyAssignment whose name that may contain an imported or exported symbol. + * + * @param node The node to substitute. + */ + function substituteShorthandPropertyAssignment(node: ShorthandPropertyAssignment) { + const name = node.name; + if (!isGeneratedIdentifier(name) && !isLocalName(name)) { + const importDeclaration = resolver.getReferencedImportDeclaration(name); + if (importDeclaration) { + if (isImportClause(importDeclaration)) { + return setTextRange( + createPropertyAssignment( + getSynthesizedClone(name), + createPropertyAccess( + getGeneratedNameForNode(importDeclaration.parent), + createIdentifier("default") + ) + ), + /*location*/ node + ); + } + else if (isImportSpecifier(importDeclaration)) { + return setTextRange( + createPropertyAssignment( + getSynthesizedClone(name), + createPropertyAccess( + getGeneratedNameForNode(importDeclaration.parent.parent.parent), + getSynthesizedClone(importDeclaration.propertyName || importDeclaration.name) + ), + ), + /*location*/ node + ); + } + } + } + return node; + } + /** * Substitute the expression, if necessary. * diff --git a/tests/baselines/reference/systemObjectShorthandRename.js b/tests/baselines/reference/systemObjectShorthandRename.js new file mode 100644 index 00000000000..a7ac63968cd --- /dev/null +++ b/tests/baselines/reference/systemObjectShorthandRename.js @@ -0,0 +1,44 @@ +//// [tests/cases/compiler/systemObjectShorthandRename.ts] //// + +//// [x.ts] +export const x = 'X' +//// [index.ts] +import {x} from './x.js' + +const x2 = {x} +const a = {x2} + +const x3 = x +const b = {x3} + +//// [x.js] +System.register([], function (exports_1, context_1) { + "use strict"; + var __moduleName = context_1 && context_1.id; + var x; + return { + setters: [], + execute: function () { + exports_1("x", x = 'X'); + } + }; +}); +//// [index.js] +System.register(["./x.js"], function (exports_1, context_1) { + "use strict"; + var __moduleName = context_1 && context_1.id; + var x_js_1, x2, a, x3, b; + return { + setters: [ + function (x_js_1_1) { + x_js_1 = x_js_1_1; + } + ], + execute: function () { + x2 = { x: x_js_1.x }; + a = { x2 }; + x3 = x_js_1.x; + b = { x3 }; + } + }; +}); diff --git a/tests/baselines/reference/systemObjectShorthandRename.symbols b/tests/baselines/reference/systemObjectShorthandRename.symbols new file mode 100644 index 00000000000..3321d047ea1 --- /dev/null +++ b/tests/baselines/reference/systemObjectShorthandRename.symbols @@ -0,0 +1,24 @@ +=== tests/cases/compiler/x.ts === +export const x = 'X' +>x : Symbol(x, Decl(x.ts, 0, 12)) + +=== tests/cases/compiler/index.ts === +import {x} from './x.js' +>x : Symbol(x, Decl(index.ts, 0, 8)) + +const x2 = {x} +>x2 : Symbol(x2, Decl(index.ts, 2, 5)) +>x : Symbol(x, Decl(index.ts, 2, 12)) + +const a = {x2} +>a : Symbol(a, Decl(index.ts, 3, 5)) +>x2 : Symbol(x2, Decl(index.ts, 3, 11)) + +const x3 = x +>x3 : Symbol(x3, Decl(index.ts, 5, 5)) +>x : Symbol(x, Decl(index.ts, 0, 8)) + +const b = {x3} +>b : Symbol(b, Decl(index.ts, 6, 5)) +>x3 : Symbol(x3, Decl(index.ts, 6, 11)) + diff --git a/tests/baselines/reference/systemObjectShorthandRename.types b/tests/baselines/reference/systemObjectShorthandRename.types new file mode 100644 index 00000000000..23f3d74c951 --- /dev/null +++ b/tests/baselines/reference/systemObjectShorthandRename.types @@ -0,0 +1,28 @@ +=== tests/cases/compiler/x.ts === +export const x = 'X' +>x : "X" +>'X' : "X" + +=== tests/cases/compiler/index.ts === +import {x} from './x.js' +>x : "X" + +const x2 = {x} +>x2 : { x: string; } +>{x} : { x: string; } +>x : string + +const a = {x2} +>a : { x2: { x: string; }; } +>{x2} : { x2: { x: string; }; } +>x2 : { x: string; } + +const x3 = x +>x3 : "X" +>x : "X" + +const b = {x3} +>b : { x3: string; } +>{x3} : { x3: string; } +>x3 : string + diff --git a/tests/cases/compiler/systemObjectShorthandRename.ts b/tests/cases/compiler/systemObjectShorthandRename.ts new file mode 100644 index 00000000000..7bc6ce56649 --- /dev/null +++ b/tests/cases/compiler/systemObjectShorthandRename.ts @@ -0,0 +1,12 @@ +// @module: system +// @target: es2015 +// @filename: x.ts +export const x = 'X' +// @filename: index.ts +import {x} from './x.js' + +const x2 = {x} +const a = {x2} + +const x3 = x +const b = {x3} \ No newline at end of file From da18a247caa852a0bc34ead3ab5175c8b6d994e3 Mon Sep 17 00:00:00 2001 From: csigs Date: Sat, 13 Jan 2018 05:10:05 +0000 Subject: [PATCH 083/172] LEGO: check in for master to temporary branch. --- .../diagnosticMessages.generated.json.lcl | 118 +++++++++++++++++- .../diagnosticMessages.generated.json.lcl | 116 ++++++++++++++++- .../diagnosticMessages.generated.json.lcl | 118 +++++++++++++++++- .../diagnosticMessages.generated.json.lcl | 118 +++++++++++++++++- .../diagnosticMessages.generated.json.lcl | 118 +++++++++++++++++- .../diagnosticMessages.generated.json.lcl | 118 +++++++++++++++++- .../diagnosticMessages.generated.json.lcl | 118 +++++++++++++++++- .../diagnosticMessages.generated.json.lcl | 118 +++++++++++++++++- .../diagnosticMessages.generated.json.lcl | 118 +++++++++++++++++- 9 files changed, 1043 insertions(+), 17 deletions(-) diff --git a/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl index 921ca600aa6..f6a140a47f7 100644 --- a/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/cht/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -474,6 +474,15 @@ + + + + + + + + + @@ -876,6 +885,12 @@ + + + + + + @@ -1281,6 +1296,24 @@ + + + + + + + + + + + + + + + + + + @@ -1830,6 +1863,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2298,6 +2358,15 @@ + + + + + + + + + @@ -2895,6 +2964,15 @@ + + + + + + + + + @@ -4539,6 +4617,12 @@ + + + + + + @@ -5574,6 +5658,15 @@ + + + + + + + + + @@ -5931,6 +6024,15 @@ + + + + + + + + + @@ -7995,6 +8097,15 @@ + + + + + + + + + @@ -8498,10 +8609,13 @@ - + - + + + + diff --git a/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl index 922edbca2de..00fd63f90f9 100644 --- a/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -474,6 +474,15 @@ + + + + + + + + + @@ -873,6 +882,12 @@ + + + + + + @@ -1278,6 +1293,24 @@ + + + + + + + + + + + + + + + + + + @@ -1827,6 +1860,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2295,6 +2355,15 @@ + + + + + + + + + @@ -2892,6 +2961,15 @@ + + + + + + + + + @@ -4536,6 +4614,12 @@ + + + + + + @@ -5568,6 +5652,15 @@ + + + + + + + + + @@ -5925,6 +6018,15 @@ + + + + + + + + + @@ -7986,6 +8088,15 @@ + + + + + + + + + @@ -8489,10 +8600,13 @@ - + + + + diff --git a/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl index efd7b273f5d..d6048a235f5 100644 --- a/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/fra/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -483,6 +483,15 @@ + + + + + + + + + @@ -885,6 +894,12 @@ + + + + + + @@ -1290,6 +1305,24 @@ + + + + + + + + + + + + + + + + + + @@ -1839,6 +1872,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2307,6 +2367,15 @@ + + + + + + + + + @@ -2904,6 +2973,15 @@ + + + + + + + + + @@ -4548,6 +4626,12 @@ + + + + + + @@ -5583,6 +5667,15 @@ + + + + + + + + + @@ -5940,6 +6033,15 @@ + + + + + + + + + @@ -8004,6 +8106,15 @@ + + + + + + + + + @@ -8507,10 +8618,13 @@ - + - + + + + diff --git a/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl index 8f52506d6b9..4d76091f527 100644 --- a/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -474,6 +474,15 @@ + + + + + + + + + @@ -876,6 +885,12 @@ + + + + + + @@ -1281,6 +1296,24 @@ + + + + + + + + + + + + + + + + + + @@ -1830,6 +1863,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2298,6 +2358,15 @@ + + + + + + + + + @@ -2895,6 +2964,15 @@ + + + + + + + + + @@ -4539,6 +4617,12 @@ + + + + + + @@ -5574,6 +5658,15 @@ + + + + + + + + + @@ -5931,6 +6024,15 @@ + + + + + + + + + @@ -7995,6 +8097,15 @@ + + + + + + + + + @@ -8498,10 +8609,13 @@ - + - + + + + diff --git a/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl index 2418583bb98..dcd9d3695fc 100644 --- a/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/kor/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -474,6 +474,15 @@ + + + + + + + + + @@ -876,6 +885,12 @@ + + + + + + @@ -1281,6 +1296,24 @@ + + + + + + + + + + + + + + + + + + @@ -1830,6 +1863,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2298,6 +2358,15 @@ + + + + + + + + + @@ -2895,6 +2964,15 @@ + + + + + + + + + @@ -4539,6 +4617,12 @@ + + + + + + @@ -5574,6 +5658,15 @@ + + + + + + + + + @@ -5931,6 +6024,15 @@ + + + + + + + + + @@ -7995,6 +8097,15 @@ + + + + + + + + + @@ -8498,10 +8609,13 @@ - + - + + + + diff --git a/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl index 42685f7a45e..a36010b60e3 100644 --- a/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/plk/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -467,6 +467,15 @@ + + + + + + + + + @@ -866,6 +875,12 @@ + + + + + + @@ -1271,6 +1286,24 @@ + + + + + + + + + + + + + + + + + + @@ -1820,6 +1853,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2288,6 +2348,15 @@ + + + + + + + + + @@ -2885,6 +2954,15 @@ + + + + + + + + + @@ -4529,6 +4607,12 @@ + + + + + + @@ -5561,6 +5645,15 @@ + + + + + + + + + @@ -5918,6 +6011,15 @@ + + + + + + + + + @@ -7979,6 +8081,15 @@ + + + + + + + + + @@ -8482,10 +8593,13 @@ - + - + + + + diff --git a/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl index bcace1bd162..8eed7be8b99 100644 --- a/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/ptb/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -467,6 +467,15 @@ + + + + + + + + + @@ -866,6 +875,12 @@ + + + + + + @@ -1271,6 +1286,24 @@ + + + + + + + + + + + + + + + + + + @@ -1820,6 +1853,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2288,6 +2348,15 @@ + + + + + + + + + @@ -2885,6 +2954,15 @@ + + + + + + + + + @@ -4529,6 +4607,12 @@ + + + + + + @@ -5561,6 +5645,15 @@ + + + + + + + + + @@ -5918,6 +6011,15 @@ + + + + + + + + + @@ -7979,6 +8081,15 @@ + + + + + + + + + @@ -8482,10 +8593,13 @@ - + - + + + + diff --git a/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl index ca1dcf2d4cb..a8529c74fd0 100644 --- a/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/rus/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -473,6 +473,15 @@ + + + + + + + + + @@ -875,6 +884,12 @@ + + + + + + @@ -1280,6 +1295,24 @@ + + + + + + + + + + + + + + + + + + @@ -1829,6 +1862,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2297,6 +2357,15 @@ + + + + + + + + + @@ -2894,6 +2963,15 @@ + + + + + + + + + @@ -4538,6 +4616,12 @@ + + + + + + @@ -5573,6 +5657,15 @@ + + + + + + + + + @@ -5930,6 +6023,15 @@ + + + + + + + + + @@ -7994,6 +8096,15 @@ + + + + + + + + + @@ -8497,10 +8608,13 @@ - + - + + + + diff --git a/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl index 34cf0f01ff6..c8c49d28117 100644 --- a/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/trk/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -467,6 +467,15 @@ + + + + + + + + + @@ -869,6 +878,12 @@ + + + + + + @@ -1274,6 +1289,24 @@ + + + + + + + + + + + + + + + + + + @@ -1823,6 +1856,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2291,6 +2351,15 @@ + + + + + + + + + @@ -2888,6 +2957,15 @@ + + + + + + + + + @@ -4532,6 +4610,12 @@ + + + + + + @@ -5567,6 +5651,15 @@ + + + + + + + + + @@ -5924,6 +6017,15 @@ + + + + + + + + + @@ -7988,6 +8090,15 @@ + + + + + + + + + @@ -8491,10 +8602,13 @@ - + - + + + + From f4cfc9db731997a9b4b2393bc21a4e1b17183b2f Mon Sep 17 00:00:00 2001 From: csigs Date: Mon, 15 Jan 2018 11:10:12 +0000 Subject: [PATCH 084/172] LEGO: check in for master to temporary branch. --- .../diagnosticMessages.generated.json.lcl | 118 +++++++++++++++++- 1 file changed, 116 insertions(+), 2 deletions(-) diff --git a/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl index c2febd1ee9b..93c54090734 100644 --- a/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl +++ b/src/loc/lcl/esn/diagnosticMessages/diagnosticMessages.generated.json.lcl @@ -483,6 +483,15 @@ + + + + + + + + + @@ -885,6 +894,12 @@ + + + + + + @@ -1290,6 +1305,24 @@ + + + + + + + + + + + + + + + + + + @@ -1839,6 +1872,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2307,6 +2367,15 @@ + + + + + + + + + @@ -2904,6 +2973,15 @@ + + + + + + + + + @@ -4548,6 +4626,12 @@ + + + + + + @@ -5583,6 +5667,15 @@ + + + + + + + + + @@ -5940,6 +6033,15 @@ + + + + + + + + + @@ -8004,6 +8106,15 @@ + + + + + + + + + @@ -8507,10 +8618,13 @@ - + - + + + + From bcb9fd7825d39774eb9657fd3ddedefb77bfd412 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Tue, 16 Jan 2018 17:53:43 +0100 Subject: [PATCH 085/172] fix: formatting for chaining methods (#21027) Closes: #20996 --- src/services/formatting/formatting.ts | 10 ++++- .../fourslash/formattingChainingMethods.ts | 37 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 tests/cases/fourslash/formattingChainingMethods.ts diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index 31f29e9ebbe..49fb1420db8 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -484,7 +484,13 @@ namespace ts.formatting { delta = Math.min(options.indentSize, parentDynamicIndentation.getDelta(node) + delta); } else if (indentation === Constants.Unknown) { - if (SmartIndenter.childStartsOnTheSameLineWithElseInIfStatement(parent, node, startLine, sourceFile)) { + if (node.kind === SyntaxKind.OpenParenToken && startLine === lastIndentedLine) { + // the is used for chaining methods formatting + // - we need to get the indentation on last line and the delta of parent + indentation = indentationOnLastIndentedLine; + delta = parentDynamicIndentation.getDelta(node); + } + else if (SmartIndenter.childStartsOnTheSameLineWithElseInIfStatement(parent, node, startLine, sourceFile)) { indentation = parentDynamicIndentation.getIndentation(); } else { @@ -622,7 +628,7 @@ namespace ts.formatting { let childContextNode = contextNode; // if there are any tokens that logically belong to node and interleave child nodes - // such tokens will be consumed in processChildNode for for the child that follows them + // such tokens will be consumed in processChildNode for the child that follows them forEachChild( node, child => { diff --git a/tests/cases/fourslash/formattingChainingMethods.ts b/tests/cases/fourslash/formattingChainingMethods.ts new file mode 100644 index 00000000000..8e8cafb14cd --- /dev/null +++ b/tests/cases/fourslash/formattingChainingMethods.ts @@ -0,0 +1,37 @@ +/// + +//// z$ = this.store.select(this.fake()) +//// .ofType( +//// 'ACTION', +//// 'ACTION-2' +//// ) +//// .pipe( +//// filter(x => !!x), +//// switchMap(() => +//// this.store.select(this.menuSelector.getAll('x')) +//// .pipe( +//// tap(x => { +//// this.x = !x; +//// }) +//// ) +//// ) +//// ); + +format.document(); +verify.currentFileContentIs(`z$ = this.store.select(this.fake()) + .ofType( + 'ACTION', + 'ACTION-2' + ) + .pipe( + filter(x => !!x), + switchMap(() => + this.store.select(this.menuSelector.getAll('x')) + .pipe( + tap(x => { + this.x = !x; + }) + ) + ) + );` +); From 76d95248660d1c7bc7f7a64d5113383f4a015712 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 16 Jan 2018 09:53:42 -0800 Subject: [PATCH 086/172] Fully deprecate the symbol display builder, reimplement in terms of node builder (#18860) * Remove SymbolWriter, give methods to EmitTextWriter * Unification of writers is done-ish * Make node builder support more flags * Write out mixins like we used to * Accept prototype-free baselines * Use instantiated constraint when building mapped type nodes * Accept better mapped type baselines * Report inaccessible this in node builder * Turns out there was a bug in our codefix, too * Symbol display builder usage falling * Replace signatureToString with a nodeBuilder solution * Replace the last internal usages of the symbol writer * Accept semicolon additions * Accept updated symbol baseline output * Start using node builder for some LS operations * Remove those pesky trailing semicolons on signatures * Get signature printing much closer to old output * Parameter lists should not be indented by default, especially when single-line * Signatures up to snuff * Type quickinfo emit is up to snuff * Start of symbol writer replacement, needs a bit more for full compat * Slightly mor accurate to old behavior * Replicate qualified name type argument output correctly * Bring back the old symbol baselines * Mostly identical to old symbol emit now * Perfectly matches old behavior thus far * Replace another usage of the symbol builder * Another usage removed * Another usage removed * Remove final uses of symbol display builder * Remove implementation and types for unused symbol display builder * Cleanup in the checker * monomorphize new emitter code * Replace emitWithSuffix * Push space character to interface with writer * List emit * Fix lack of usage of emitExpression * writeList, not printList * Remove listy writes and replace with new printer calls * Move ListFormat into types.ts * Remove most new XToString functions in favor of node builder functions * Accept API breaks * Add getSymbolDisplayBuilder polyfill * Accept updated API baseline * Move definition to make diff easier to read * Reinternalize some things * Remove trailign whitespace * Reorder for zero diff * Remove newline * Make shim mor eperfectly imitate old behavior * Style feedback * Rename reset to clear to maintain backcompat with SymbolWriter * Fix quickfix * Keep EmitTextWriter internal * Remove EmitTextWriter from public API * Mimic default name declaration emit fix * Fix tests broken by merge * use isFunctionLike * Cleanup, sync TypeFormat and NodeBuilder flags * Reorder Node initialization so pos and end are first, so a TextRange hidden class is made first to reduce future polymorphism * Use variable instead of ternary * Write helper for emitNodeWithWriter * Emitter cleanup * Cleanup whitespace, comment * Reuse printer * Raise error if display parts writer uses rawWrite * Hide writer parameter through different function instead of overload, rename function in emitter * Make less printer * fix lint --- src/compiler/checker.ts | 1334 ++++++----------- src/compiler/core.ts | 8 +- src/compiler/declarationEmitter.ts | 10 +- src/compiler/emitter.ts | 891 ++++++----- src/compiler/factory.ts | 15 +- src/compiler/sourcemap.ts | 8 +- src/compiler/types.ts | 309 +++- src/compiler/utilities.ts | 37 +- src/compiler/visitor.ts | 4 +- src/services/codefixes/inferFromUsage.ts | 17 +- src/services/services.ts | 3 +- src/services/signatureHelp.ts | 42 +- src/services/symbolDisplay.ts | 18 +- src/services/textChanges.ts | 36 +- src/services/utilities.ts | 20 +- .../reference/api/tsserverlibrary.d.ts | 208 ++- tests/baselines/reference/api/typescript.d.ts | 208 ++- .../reference/commentOnParameter1.js | 12 +- .../reference/commentOnParameter2.js | 10 +- tests/baselines/reference/commentsFunction.js | 6 +- .../reference/declFileConstructors.js | 8 +- .../baselines/reference/declFileFunctions.js | 12 +- tests/baselines/reference/declFileMethods.js | 32 +- .../declarationEmitBindingPatterns.js | 2 +- .../declarationEmitDestructuring2.js | 28 +- .../declarationEmitIndexTypeArray.js | 2 +- ...declarationEmitTypeofDefaultExport.symbols | 4 +- .../reference/deferredLookupTypeResolution.js | 4 +- .../deferredLookupTypeResolution.types | 16 +- .../deferredLookupTypeResolution2.errors.txt | 8 +- .../deferredLookupTypeResolution2.types | 28 +- .../isomorphicMappedTypeInference.js | 4 +- ...ngNamedPropertyOfIllegalCharacters.symbols | 4 +- ...nfoDisplayPartsLiteralLikeNames01.baseline | 6 +- .../reference/recursiveTypeRelations.types | 2 +- .../typeGuardFunctionOfFormThisErrors.js | 2 +- ...odeFixClassImplementInterfaceMappedType.ts | 2 +- 37 files changed, 1750 insertions(+), 1610 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5e26b37b61a..7ab50f7be60 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -128,6 +128,11 @@ namespace ts { typeToTypeNode: nodeBuilder.typeToTypeNode, indexInfoToIndexSignatureDeclaration: nodeBuilder.indexInfoToIndexSignatureDeclaration, signatureToSignatureDeclaration: nodeBuilder.signatureToSignatureDeclaration, + symbolToEntityName: nodeBuilder.symbolToEntityName, + symbolToExpression: nodeBuilder.symbolToExpression, + symbolToTypeParameterDeclarations: nodeBuilder.symbolToTypeParameterDeclarations, + symbolToParameterDeclaration: nodeBuilder.symbolToParameterDeclaration, + typeParameterToDeclaration: nodeBuilder.typeParameterToDeclaration, getSymbolsInScope: (location, meaning) => { location = getParseTreeNode(location); return location ? getSymbolsInScope(location, meaning) : []; @@ -155,16 +160,31 @@ namespace ts { location = getParseTreeNode(location, isIdentifier); return location ? getPropertySymbolOfDestructuringAssignment(location) : undefined; }, - signatureToString: (signature, enclosingDeclaration?, flags?, kind?) => { + signatureToString: (signature, enclosingDeclaration, flags, kind) => { return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind); }, - typeToString: (type, enclosingDeclaration?, flags?) => { + typeToString: (type, enclosingDeclaration, flags) => { return typeToString(type, getParseTreeNode(enclosingDeclaration), flags); }, - getSymbolDisplayBuilder, - symbolToString: (symbol, enclosingDeclaration?, meaning?) => { - return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning); + symbolToString: (symbol, enclosingDeclaration, meaning, flags) => { + return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags); }, + typePredicateToString: (predicate, enclosingDeclaration, flags) => { + return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags); + }, + writeSignature: (signature, enclosingDeclaration, flags, kind, writer) => { + return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind, writer); + }, + writeType: (type, enclosingDeclaration, flags, writer) => { + return typeToString(type, getParseTreeNode(enclosingDeclaration), flags, writer); + }, + writeSymbol: (symbol, enclosingDeclaration, meaning, flags, writer) => { + return symbolToString(symbol, getParseTreeNode(enclosingDeclaration), meaning, flags, writer); + }, + writeTypePredicate: (predicate, enclosingDeclaration, flags, writer) => { + return typePredicateToString(predicate, getParseTreeNode(enclosingDeclaration), flags, writer); + }, + getSymbolDisplayBuilder, // TODO (weswigham): Remove once deprecation process is complete getAugmentedPropertiesOfType, getRootSymbols, getContextualType: node => { @@ -273,6 +293,7 @@ namespace ts { }, getJsxNamespace: () => unescapeLeadingUnderscores(getJsxNamespace()), getAccessibleSymbolChain, + getTypePredicateOfSignature, resolveExternalModuleSymbol, }; @@ -520,9 +541,6 @@ namespace ts { const identityRelation = createMap(); const enumRelation = createMap(); - // This is for caching the result of getSymbolDisplayBuilder. Do not access directly. - let _displayBuilder: SymbolDisplayBuilder; - type TypeSystemEntity = Symbol | Type | Signature; const enum TypeSystemPropertyName { @@ -572,6 +590,145 @@ namespace ts { return checker; + /** + * @deprecated + */ + function getSymbolDisplayBuilder(): SymbolDisplayBuilder { + return { + buildTypeDisplay(type, writer, enclosingDeclaration?, flags?) { + typeToString(type, enclosingDeclaration, flags, emitTextWriterWrapper(writer)); + }, + buildSymbolDisplay(symbol, writer, enclosingDeclaration?, meaning?, flags?) { + symbolToString(symbol, enclosingDeclaration, meaning, flags | SymbolFormatFlags.AllowAnyNodeKind, emitTextWriterWrapper(writer)); + }, + buildSignatureDisplay(signature, writer, enclosing?, flags?, kind?) { + signatureToString(signature, enclosing, flags, kind, emitTextWriterWrapper(writer)); + }, + buildIndexSignatureDisplay(info, writer, kind, enclosing?, flags?) { + const sig = nodeBuilder.indexInfoToIndexSignatureDeclaration(info, kind, enclosing, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors, writer); + const printer = createPrinter({ removeComments: true }); + printer.writeNode(EmitHint.Unspecified, sig, getSourceFileOfNode(getParseTreeNode(enclosing)), emitTextWriterWrapper(writer)); + }, + buildParameterDisplay(symbol, writer, enclosing?, flags?) { + const node = nodeBuilder.symbolToParameterDeclaration(symbol, enclosing, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors, writer); + const printer = createPrinter({ removeComments: true }); + printer.writeNode(EmitHint.Unspecified, node, getSourceFileOfNode(getParseTreeNode(enclosing)), emitTextWriterWrapper(writer)); + }, + buildTypeParameterDisplay(tp, writer, enclosing?, flags?) { + const node = nodeBuilder.typeParameterToDeclaration(tp, enclosing, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.OmitParameterModifiers, writer); + const printer = createPrinter({ removeComments: true }); + printer.writeNode(EmitHint.Unspecified, node, getSourceFileOfNode(getParseTreeNode(enclosing)), emitTextWriterWrapper(writer)); + }, + buildTypePredicateDisplay(predicate, writer, enclosing?, flags?) { + typePredicateToString(predicate, enclosing, flags, emitTextWriterWrapper(writer)); + }, + buildTypeParameterDisplayFromSymbol(symbol, writer, enclosing?, flags?) { + const nodes = nodeBuilder.symbolToTypeParameterDeclarations(symbol, enclosing, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors, writer); + const printer = createPrinter({ removeComments: true }); + printer.writeList(ListFormat.TypeParameters, nodes, getSourceFileOfNode(getParseTreeNode(enclosing)), emitTextWriterWrapper(writer)); + }, + buildDisplayForParametersAndDelimiters(thisParameter, parameters, writer, enclosing?, originalFlags?) { + const printer = createPrinter({ removeComments: true }); + const flags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors | toNodeBuilderFlags(originalFlags); + const thisParameterArray = thisParameter ? [nodeBuilder.symbolToParameterDeclaration(thisParameter, enclosing, flags)] : []; + const params = createNodeArray([...thisParameterArray, ...map(parameters, param => nodeBuilder.symbolToParameterDeclaration(param, enclosing, flags))]); + printer.writeList(ListFormat.CallExpressionArguments, params, getSourceFileOfNode(getParseTreeNode(enclosing)), emitTextWriterWrapper(writer)); + }, + buildDisplayForTypeParametersAndDelimiters(typeParameters, writer, enclosing?, flags?) { + const printer = createPrinter({ removeComments: true }); + const args = createNodeArray(map(typeParameters, p => nodeBuilder.typeParameterToDeclaration(p, enclosing, toNodeBuilderFlags(flags)))); + printer.writeList(ListFormat.TypeParameters, args, getSourceFileOfNode(getParseTreeNode(enclosing)), emitTextWriterWrapper(writer)); + }, + buildReturnTypeDisplay(signature, writer, enclosing?, flags?) { + writer.writePunctuation(":"); + writer.writeSpace(" "); + const predicate = getTypePredicateOfSignature(signature); + if (predicate) { + return typePredicateToString(predicate, enclosing, flags, emitTextWriterWrapper(writer)); + } + const node = nodeBuilder.typeToTypeNode(getReturnTypeOfSignature(signature), enclosing, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors, writer); + const printer = createPrinter({ removeComments: true }); + printer.writeNode(EmitHint.Unspecified, node, getSourceFileOfNode(getParseTreeNode(enclosing)), emitTextWriterWrapper(writer)); + } + }; + + function emitTextWriterWrapper(underlying: SymbolWriter): EmitTextWriter { + return { + write: ts.noop, + writeTextOfNode: ts.noop, + writeLine: ts.noop, + increaseIndent() { + return underlying.increaseIndent(); + }, + decreaseIndent() { + return underlying.decreaseIndent(); + }, + getText() { + return ""; + }, + rawWrite: ts.noop, + writeLiteral(s) { + return underlying.writeStringLiteral(s); + }, + getTextPos() { + return 0; + }, + getLine() { + return 0; + }, + getColumn() { + return 0; + }, + getIndent() { + return 0; + }, + isAtStartOfLine() { + return false; + }, + clear() { + return underlying.clear(); + }, + + writeKeyword(text) { + return underlying.writeKeyword(text); + }, + writeOperator(text) { + return underlying.writeOperator(text); + }, + writePunctuation(text) { + return underlying.writePunctuation(text); + }, + writeSpace(text) { + return underlying.writeSpace(text); + }, + writeStringLiteral(text) { + return underlying.writeStringLiteral(text); + }, + writeParameter(text) { + return underlying.writeParameter(text); + }, + writeProperty(text) { + return underlying.writeProperty(text); + }, + writeSymbol(text, symbol) { + return underlying.writeSymbol(text, symbol); + }, + trackSymbol(symbol, enclosing?, meaning?) { + return underlying.trackSymbol && underlying.trackSymbol(symbol, enclosing, meaning); + }, + reportInaccessibleThisError() { + return underlying.reportInaccessibleThisError && underlying.reportInaccessibleThisError(); + }, + reportPrivateInBaseOfClassExpression(name) { + return underlying.reportPrivateInBaseOfClassExpression && underlying.reportPrivateInBaseOfClassExpression(name); + }, + reportInaccessibleUniqueSymbolError() { + return underlying.reportInaccessibleUniqueSymbolError && underlying.reportInaccessibleUniqueSymbolError(); + } + }; + } + } + function getJsxNamespace(): __String { if (!_jsxNamespace) { _jsxNamespace = "React" as __String; @@ -2516,100 +2673,130 @@ namespace ts { }; } - function writeKeyword(writer: SymbolWriter, kind: SyntaxKind) { - writer.writeKeyword(tokenToString(kind)); + function symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags: SymbolFormatFlags = SymbolFormatFlags.AllowAnyNodeKind, writer?: EmitTextWriter): string { + let nodeFlags = NodeBuilderFlags.IgnoreErrors; + if (flags & SymbolFormatFlags.UseOnlyExternalAliasing) { + nodeFlags |= NodeBuilderFlags.UseOnlyExternalAliasing; + } + if (flags & SymbolFormatFlags.WriteTypeParametersOrArguments) { + nodeFlags |= NodeBuilderFlags.WriteTypeParametersInQualifiedName; + } + const builder = flags & SymbolFormatFlags.AllowAnyNodeKind ? nodeBuilder.symbolToExpression : nodeBuilder.symbolToEntityName; + return writer ? symbolToStringWorker(writer).getText() : usingSingleLineStringWriter(symbolToStringWorker); + + function symbolToStringWorker(writer: EmitTextWriter) { + const entity = builder(symbol, meaning, enclosingDeclaration, nodeFlags); + const printer = createPrinter({ removeComments: true }); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, entity, /*sourceFile*/ sourceFile, writer); + return writer; + } } - function writePunctuation(writer: SymbolWriter, kind: SyntaxKind) { - writer.writePunctuation(tokenToString(kind)); + function signatureToString(signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind, writer?: EmitTextWriter): string { + return writer ? signatureToStringWorker(writer).getText() : usingSingleLineStringWriter(signatureToStringWorker); + + function signatureToStringWorker(writer: EmitTextWriter) { + let sigOutput: SyntaxKind; + if (flags & TypeFormatFlags.WriteArrowStyleSignature) { + sigOutput = kind === SignatureKind.Construct ? SyntaxKind.ConstructorType : SyntaxKind.FunctionType; + } + else { + sigOutput = kind === SignatureKind.Construct ? SyntaxKind.ConstructSignature : SyntaxKind.CallSignature; + } + const sig = nodeBuilder.signatureToSignatureDeclaration(signature, sigOutput, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName); + const printer = createPrinter({ removeComments: true, omitTrailingSemicolon: true }); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, sig, /*sourceFile*/ sourceFile, writer); + return writer; + } } - function writeSpace(writer: SymbolWriter) { - writer.writeSpace(" "); - } - - function symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): string { - return usingSingleLineStringWriter(writer => { - getSymbolDisplayBuilder().buildSymbolDisplay(symbol, writer, enclosingDeclaration, meaning); - }); - } - - function signatureToString(signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): string { - return usingSingleLineStringWriter(writer => { - getSymbolDisplayBuilder().buildSignatureDisplay(signature, writer, enclosingDeclaration, flags, kind); - }); - } - - function typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string { - const typeNode = nodeBuilder.typeToTypeNode(type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName); + function typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags, writer: EmitTextWriter = createTextWriter("")): string { + const typeNode = nodeBuilder.typeToTypeNode(type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors, writer); Debug.assert(typeNode !== undefined, "should always get typenode"); const options = { removeComments: true }; - const writer = createTextWriter(""); const printer = createPrinter(options); const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ sourceFile, writer); const result = writer.getText(); const maxLength = compilerOptions.noErrorTruncation || flags & TypeFormatFlags.NoTruncation ? undefined : 100; - if (maxLength && result.length >= maxLength) { + if (maxLength && result && result.length >= maxLength) { return result.substr(0, maxLength - "...".length) + "..."; } return result; + } - function toNodeBuilderFlags(flags?: TypeFormatFlags): NodeBuilderFlags { - let result = NodeBuilderFlags.None; - if (!flags) { - return result; - } - if (flags & TypeFormatFlags.NoTruncation) { - result |= NodeBuilderFlags.NoTruncation; - } - if (flags & TypeFormatFlags.UseFullyQualifiedType) { - result |= NodeBuilderFlags.UseFullyQualifiedType; - } - if (flags & TypeFormatFlags.SuppressAnyReturnType) { - result |= NodeBuilderFlags.SuppressAnyReturnType; - } - if (flags & TypeFormatFlags.WriteArrayAsGenericType) { - result |= NodeBuilderFlags.WriteArrayAsGenericType; - } - if (flags & TypeFormatFlags.WriteTypeArgumentsOfSignature) { - result |= NodeBuilderFlags.WriteTypeArgumentsOfSignature; - } - - return result; - } + function toNodeBuilderFlags(flags?: TypeFormatFlags): NodeBuilderFlags { + return flags & TypeFormatFlags.NodeBuilderFlagsMask; } function createNodeBuilder() { return { - typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags) => { + typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => { Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0); - const context = createNodeBuilderContext(enclosingDeclaration, flags); + const context = createNodeBuilderContext(enclosingDeclaration, flags, tracker); const resultingNode = typeToTypeNodeHelper(type, context); const result = context.encounteredError ? undefined : resultingNode; return result; }, - indexInfoToIndexSignatureDeclaration: (indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags) => { + indexInfoToIndexSignatureDeclaration: (indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => { Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0); - const context = createNodeBuilderContext(enclosingDeclaration, flags); + const context = createNodeBuilderContext(enclosingDeclaration, flags, tracker); const resultingNode = indexInfoToIndexSignatureDeclarationHelper(indexInfo, kind, context); const result = context.encounteredError ? undefined : resultingNode; return result; }, - signatureToSignatureDeclaration: (signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags) => { + signatureToSignatureDeclaration: (signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => { Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0); - const context = createNodeBuilderContext(enclosingDeclaration, flags); + const context = createNodeBuilderContext(enclosingDeclaration, flags, tracker); const resultingNode = signatureToSignatureDeclarationHelper(signature, kind, context); const result = context.encounteredError ? undefined : resultingNode; return result; - } + }, + symbolToEntityName: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => { + Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0); + const context = createNodeBuilderContext(enclosingDeclaration, flags, tracker); + const resultingNode = symbolToName(symbol, context, meaning, /*expectsIdentifier*/ false); + const result = context.encounteredError ? undefined : resultingNode; + return result; + }, + symbolToExpression: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => { + Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0); + const context = createNodeBuilderContext(enclosingDeclaration, flags, tracker); + const resultingNode = symbolToExpression(symbol, context, meaning); + const result = context.encounteredError ? undefined : resultingNode; + return result; + }, + symbolToTypeParameterDeclarations: (symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => { + Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0); + const context = createNodeBuilderContext(enclosingDeclaration, flags, tracker); + const resultingNode = typeParametersToTypeParameterDeclarations(symbol, context); + const result = context.encounteredError ? undefined : resultingNode; + return result; + }, + symbolToParameterDeclaration: (symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => { + Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0); + const context = createNodeBuilderContext(enclosingDeclaration, flags, tracker); + const resultingNode = symbolToParameterDeclaration(symbol, context); + const result = context.encounteredError ? undefined : resultingNode; + return result; + }, + typeParameterToDeclaration: (parameter: TypeParameter, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => { + Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0); + const context = createNodeBuilderContext(enclosingDeclaration, flags, tracker); + const resultingNode = typeParameterToDeclaration(parameter, context); + const result = context.encounteredError ? undefined : resultingNode; + return result; + }, }; - function createNodeBuilderContext(enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): NodeBuilderContext { + function createNodeBuilderContext(enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined, tracker: SymbolTracker | undefined): NodeBuilderContext { return { enclosingDeclaration, flags, + tracker: tracker && tracker.trackSymbol ? tracker : { trackSymbol: noop }, encounteredError: false, symbolStack: undefined }; @@ -2656,6 +2843,11 @@ namespace ts { return (type).intrinsicName === "true" ? createTrue() : createFalse(); } if (type.flags & TypeFlags.UniqueESSymbol) { + if (!(context.flags & NodeBuilderFlags.AllowUniqueESSymbolType)) { + if (context.tracker.reportInaccessibleUniqueSymbolError) { + context.tracker.reportInaccessibleUniqueSymbolError(); + } + } return createTypeOperatorNode(SyntaxKind.UniqueKeyword, createKeywordTypeNode(SyntaxKind.SymbolKeyword)); } if (type.flags & TypeFlags.Void) { @@ -2681,6 +2873,9 @@ namespace ts { if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowThisInObjectLiteral)) { context.encounteredError = true; } + if (context.tracker.reportInaccessibleThisError) { + context.tracker.reportInaccessibleThisError(); + } } return createThis(); } @@ -2696,7 +2891,7 @@ namespace ts { // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. return createTypeReferenceNode(name, /*typeArguments*/ undefined); } - if (!inTypeAlias && type.aliasSymbol && isTypeSymbolAccessible(type.aliasSymbol, context.enclosingDeclaration)) { + if (!inTypeAlias && type.aliasSymbol && (context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope || isTypeSymbolAccessible(type.aliasSymbol, context.enclosingDeclaration))) { const name = symbolToTypeReferenceName(type.aliasSymbol); const typeArgumentNodes = mapToTypeNodes(type.aliasTypeArguments, context); return createTypeReferenceNode(name, typeArgumentNodes); @@ -2737,7 +2932,7 @@ namespace ts { Debug.assert(!!(type.flags & TypeFlags.Object)); const readonlyToken = type.declaration && type.declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; const questionToken = type.declaration && type.declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; - const typeParameterNode = typeParameterToDeclaration(getTypeParameterFromMappedType(type), context); + const typeParameterNode = typeParameterToDeclaration(getTypeParameterFromMappedType(type), context, getConstraintTypeFromMappedType(type)); const templateTypeNode = typeToTypeNodeHelper(getTemplateTypeFromMappedType(type), context); const mappedTypeNode = createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode); @@ -2748,7 +2943,7 @@ namespace ts { const symbol = type.symbol; if (symbol) { // Always use 'typeof T' for type of class, enum, and module objects - if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) || + if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) && !(symbol.valueDeclaration.kind === SyntaxKind.ClassExpression && context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) || symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || shouldWriteTypeOfFunctionSymbol()) { return createTypeQueryNodeFromSymbol(symbol, SymbolFlags.Value); @@ -2771,10 +2966,17 @@ namespace ts { if (!context.symbolStack) { context.symbolStack = []; } - context.symbolStack.push(symbol); - const result = createTypeNodeFromObjectType(type); - context.symbolStack.pop(); - return result; + + const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class; + if (isConstructorObject) { + return createTypeNodeFromObjectType(type); + } + else { + context.symbolStack.push(symbol); + const result = createTypeNodeFromObjectType(type); + context.symbolStack.pop(); + return result; + } } } else { @@ -2791,7 +2993,7 @@ namespace ts { declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { // typeof is allowed only for static/non local functions - return contains(context.symbolStack, symbol); // it is type of the symbol uses itself recursively + return !!(context.flags & NodeBuilderFlags.UseTypeOfFunction) || contains(context.symbolStack, symbol); // it is type of the symbol uses itself recursively } } } @@ -2826,7 +3028,7 @@ namespace ts { const members = createTypeNodesFromResolvedType(resolved); context.flags = savedFlags; const typeLiteralNode = createTypeLiteralNode(members); - return setEmitFlags(typeLiteralNode, EmitFlags.SingleLine); + return setEmitFlags(typeLiteralNode, (context.flags & NodeBuilderFlags.MultilineObjectLiterals) ? 0 : EmitFlags.SingleLine); } function createTypeQueryNodeFromSymbol(symbol: Symbol, symbolFlags: SymbolFlags) { @@ -2864,6 +3066,11 @@ namespace ts { context.encounteredError = true; return undefined; } + else if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral && + type.symbol.valueDeclaration && + type.symbol.valueDeclaration.kind === SyntaxKind.ClassExpression) { + return createAnonymousTypeNode(type); + } else { const outerTypeParameters = type.target.outerTypeParameters; let i = 0; @@ -2965,9 +3172,24 @@ namespace ts { } for (const propertySymbol of properties) { + if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) { + if (propertySymbol.flags & SymbolFlags.Prototype) { + continue; + } + if (getDeclarationModifierFlagsFromSymbol(propertySymbol) & (ModifierFlags.Private | ModifierFlags.Protected) && context.tracker.reportPrivateInBaseOfClassExpression) { + context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName)); + } + } const propertyType = getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped ? anyType : getTypeOfSymbol(propertySymbol); const saveEnclosingDeclaration = context.enclosingDeclaration; context.enclosingDeclaration = undefined; + if (getCheckFlags(propertySymbol) & CheckFlags.Late) { + const decl = firstOrUndefined(propertySymbol.declarations); + const name = hasLateBindableName(decl) && resolveEntityName(decl.name.expression, SymbolFlags.Value); + if (name && context.tracker.trackSymbol) { + context.tracker.trackSymbol(name, saveEnclosingDeclaration, SymbolFlags.Value); + } + } const propertyName = symbolToName(propertySymbol, context, SymbolFlags.Value, /*expectsIdentifier*/ true); context.enclosingDeclaration = saveEnclosingDeclaration; const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? createToken(SyntaxKind.QuestionToken) : undefined; @@ -3023,7 +3245,10 @@ namespace ts { /*questionToken*/ undefined, indexerTypeNode, /*initializer*/ undefined); - const typeNode = typeToTypeNodeHelper(indexInfo.type, context); + const typeNode = indexInfo.type ? typeToTypeNodeHelper(indexInfo.type, context) : typeToTypeNodeHelper(anyType, context); + if (!indexInfo.type && !(context.flags & NodeBuilderFlags.AllowEmptyIndexInfoType)) { + context.encounteredError = true; + } return createIndexSignature( /*decorators*/ undefined, indexInfo.isReadonly ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined, @@ -3032,7 +3257,14 @@ namespace ts { } function signatureToSignatureDeclarationHelper(signature: Signature, kind: SyntaxKind, context: NodeBuilderContext): SignatureDeclaration { - const typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, context)); + let typeParameters: TypeParameterDeclaration[]; + let typeArguments: TypeNode[]; + if (context.flags & NodeBuilderFlags.WriteTypeArgumentsOfSignature && signature.target && signature.mapper && signature.target.typeParameters) { + typeArguments = signature.target.typeParameters.map(parameter => typeToTypeNodeHelper(instantiateType(parameter, signature.mapper), context)); + } + else { + typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, context)); + } const parameters = signature.parameters.map(parameter => symbolToParameterDeclaration(parameter, context)); if (signature.thisParameter) { const thisParameter = symbolToParameterDeclaration(signature.thisParameter, context); @@ -3059,15 +3291,17 @@ namespace ts { else if (!returnTypeNode) { returnTypeNode = createKeywordTypeNode(SyntaxKind.AnyKeyword); } - return createSignatureDeclaration(kind, typeParameters, parameters, returnTypeNode); + return createSignatureDeclaration(kind, typeParameters, parameters, returnTypeNode, typeArguments); } - function typeParameterToDeclaration(type: TypeParameter, context: NodeBuilderContext): TypeParameterDeclaration { + function typeParameterToDeclaration(type: TypeParameter, context: NodeBuilderContext, constraint = getConstraintFromTypeParameter(type)): TypeParameterDeclaration { + const savedContextFlags = context.flags; + context.flags &= ~NodeBuilderFlags.WriteTypeParametersInQualifiedName; // Avoids potential infinite loop when building for a claimspace with a generic const name = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true); - const constraint = getConstraintFromTypeParameter(type); const constraintNode = constraint && typeToTypeNodeHelper(constraint, context); const defaultParameter = getDefaultFromTypeParameter(type); const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter, context); + context.flags = savedContextFlags; return createTypeParameterDeclaration(name, constraintNode, defaultParameterNode); } @@ -3081,7 +3315,7 @@ namespace ts { } const parameterTypeNode = typeToTypeNodeHelper(parameterType, context); - const modifiers = parameterDeclaration && parameterDeclaration.modifiers && parameterDeclaration.modifiers.map(getSynthesizedClone); + const modifiers = !(context.flags & NodeBuilderFlags.OmitParameterModifiers) && parameterDeclaration && parameterDeclaration.modifiers && parameterDeclaration.modifiers.map(getSynthesizedClone); const dotDotDotToken = !parameterDeclaration || isRestParameter(parameterDeclaration) ? createToken(SyntaxKind.DotDotDotToken) : undefined; const name = parameterDeclaration ? parameterDeclaration.name ? @@ -3114,10 +3348,8 @@ namespace ts { } } - function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: true): Identifier; - function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: false): EntityName; - function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: boolean): EntityName { - + function lookupSymbolChain(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags) { + context.tracker.trackSymbol(symbol, context.enclosingDeclaration, meaning); // Try to get qualified name if the symbol is not a type parameter and there is an enclosing declaration. let chain: Symbol[]; const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; @@ -3128,42 +3360,11 @@ namespace ts { else { chain = [symbol]; } - - if (expectsIdentifier && chain.length !== 1 - && !context.encounteredError - && !(context.flags & NodeBuilderFlags.AllowQualifedNameInPlaceOfIdentifier)) { - context.encounteredError = true; - } - return createEntityNameFromSymbolChain(chain, chain.length - 1); - - function createEntityNameFromSymbolChain(chain: Symbol[], index: number): EntityName { - Debug.assert(chain && 0 <= index && index < chain.length); - const symbol = chain[index]; - let typeParameterNodes: ReadonlyArray | undefined; - if (context.flags & NodeBuilderFlags.WriteTypeParametersInQualifiedName && index > 0) { - const parentSymbol = chain[index - 1]; - let typeParameters: TypeParameter[]; - if (getCheckFlags(symbol) & CheckFlags.Instantiated) { - typeParameters = getTypeParametersOfClassOrInterface(parentSymbol); - } - else { - const targetSymbol = getTargetSymbol(parentSymbol); - if (targetSymbol.flags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeAlias)) { - typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); - } - } - - typeParameterNodes = mapToTypeNodes(typeParameters, context); - } - - const identifier = setEmitFlags(createIdentifier(getNameOfSymbolAsWritten(symbol, context), typeParameterNodes), EmitFlags.NoAsciiEscaping); - - return index > 0 ? createQualifiedName(createEntityNameFromSymbolChain(chain, index - 1), identifier) : identifier; - } + return chain; /** @param endOfChain Set to false for recursive calls; non-recursive calls should always output something. */ function getSymbolChain(symbol: Symbol, meaning: SymbolFlags, endOfChain: boolean): Symbol[] | undefined { - let accessibleSymbolChain = getAccessibleSymbolChain(symbol, context.enclosingDeclaration, meaning, /*useOnlyExternalAliasing*/ false); + let accessibleSymbolChain = getAccessibleSymbolChain(symbol, context.enclosingDeclaration, meaning, !!(context.flags & NodeBuilderFlags.UseOnlyExternalAliasing)); let parentSymbol: Symbol; if (!accessibleSymbolChain || @@ -3195,12 +3396,113 @@ namespace ts { } } } + + function typeParametersToTypeParameterDeclarations(symbol: Symbol, context: NodeBuilderContext) { + let typeParameterNodes: NodeArray | undefined; + const targetSymbol = getTargetSymbol(symbol); + if (targetSymbol.flags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeAlias)) { + typeParameterNodes = createNodeArray(map(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol), tp => typeParameterToDeclaration(tp, context))); + } + return typeParameterNodes; + } + + function lookupTypeParameterNodes(chain: Symbol[], index: number, context: NodeBuilderContext) { + Debug.assert(chain && 0 <= index && index < chain.length); + const symbol = chain[index]; + let typeParameterNodes: ReadonlyArray | ReadonlyArray | undefined; + if (context.flags & NodeBuilderFlags.WriteTypeParametersInQualifiedName && index < (chain.length - 1)) { + const parentSymbol = symbol; + const nextSymbol = chain[index + 1]; + if (getCheckFlags(nextSymbol) & CheckFlags.Instantiated) { + const params = getTypeParametersOfClassOrInterface( + parentSymbol.flags & SymbolFlags.Alias ? resolveAlias(parentSymbol) : parentSymbol + ); + typeParameterNodes = mapToTypeNodes(map(params, (nextSymbol as TransientSymbol).mapper), context); + } + else { + typeParameterNodes = typeParametersToTypeParameterDeclarations(symbol, context); + } + } + return typeParameterNodes; + } + + function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: true): Identifier; + function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: false): EntityName; + function symbolToName(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, expectsIdentifier: boolean): EntityName { + const chain = lookupSymbolChain(symbol, context, meaning); + + if (expectsIdentifier && chain.length !== 1 + && !context.encounteredError + && !(context.flags & NodeBuilderFlags.AllowQualifedNameInPlaceOfIdentifier)) { + context.encounteredError = true; + } + return createEntityNameFromSymbolChain(chain, chain.length - 1); + + function createEntityNameFromSymbolChain(chain: Symbol[], index: number): EntityName { + const typeParameterNodes = lookupTypeParameterNodes(chain, index, context); + const symbol = chain[index]; + const symbolName = getNameOfSymbolAsWritten(symbol, context); + const identifier = setEmitFlags(createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); + identifier.symbol = symbol; + + return index > 0 ? createQualifiedName(createEntityNameFromSymbolChain(chain, index - 1), identifier) : identifier; + } + } + + function symbolToExpression(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags) { + const chain = lookupSymbolChain(symbol, context, meaning); + + return createExpressionFromSymbolChain(chain, chain.length - 1); + + function createExpressionFromSymbolChain(chain: Symbol[], index: number): Expression { + const typeParameterNodes = lookupTypeParameterNodes(chain, index, context); + const symbol = chain[index]; + + let symbolName = getNameOfSymbolAsWritten(symbol, context); + let firstChar = symbolName.charCodeAt(0); + const canUsePropertyAccess = isIdentifierStart(firstChar, languageVersion); + if (index === 0 || canUsePropertyAccess) { + const identifier = setEmitFlags(createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); + identifier.symbol = symbol; + + return index > 0 ? createPropertyAccess(createExpressionFromSymbolChain(chain, index - 1), identifier) : identifier; + } + else { + if (firstChar === CharacterCodes.openBracket) { + symbolName = symbolName.substring(1, symbolName.length - 1); + firstChar = symbolName.charCodeAt(0); + } + let expression: Expression; + if (isSingleOrDoubleQuote(firstChar)) { + expression = createLiteral(symbolName.substring(1, symbolName.length - 1).replace(/\\./g, s => s.substring(1))); + (expression as StringLiteral).singleQuote = firstChar === CharacterCodes.singleQuote; + } + else if (("" + +symbolName) === symbolName) { + expression = createLiteral(+symbolName); + } + if (!expression) { + expression = setEmitFlags(createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); + expression.symbol = symbol; + } + return createElementAccess(createExpressionFromSymbolChain(chain, index - 1), expression); + } + } + } } - function typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Declaration, flags?: TypeFormatFlags): string { - return usingSingleLineStringWriter(writer => { - getSymbolDisplayBuilder().buildTypePredicateDisplay(typePredicate, writer, enclosingDeclaration, flags); - }); + function typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Node, flags?: TypeFormatFlags, writer?: EmitTextWriter): string { + return writer ? typePredicateToStringWorker(writer).getText() : usingSingleLineStringWriter(typePredicateToStringWorker); + + function typePredicateToStringWorker(writer: EmitTextWriter) { + const predicate = createTypePredicateNode( + typePredicate.kind === TypePredicateKind.Identifier ? createIdentifier(typePredicate.parameterName) : createThisTypeNode(), + nodeBuilder.typeToTypeNode(typePredicate.type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName) + ); + const printer = createPrinter({ removeComments: true }); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, predicate, /*sourceFile*/ sourceFile, writer); + return writer; + } } function formatUnionTypes(types: Type[]): Type[] { @@ -3262,6 +3564,7 @@ namespace ts { interface NodeBuilderContext { enclosingDeclaration: Node | undefined; flags: NodeBuilderFlags | undefined; + tracker: SymbolTracker | undefined; // State encounteredError: boolean; @@ -3276,6 +3579,9 @@ namespace ts { * It will also use a representation of a number as written instead of a decimal form, e.g. `0o11` instead of `9`. */ function getNameOfSymbolAsWritten(symbol: Symbol, context?: NodeBuilderContext): string { + if (context && context.flags & NodeBuilderFlags.WriteDefaultSymbolWithoutName && symbol.escapedName === InternalSymbolName.Default) { + return "default"; + } if (symbol.declarations && symbol.declarations.length) { const declaration = symbol.declarations[0]; const name = getNameOfDeclaration(declaration); @@ -3305,781 +3611,6 @@ namespace ts { return symbolName(symbol); } - function getSymbolDisplayBuilder(): SymbolDisplayBuilder { - - /** - * Writes only the name of the symbol out to the writer. Uses the original source text - * for the name of the symbol if it is available to match how the user wrote the name. - */ - function appendSymbolNameOnly(symbol: Symbol, writer: SymbolWriter): void { - writer.writeSymbol(getNameOfSymbolAsWritten(symbol), symbol); - } - - /** - * Writes a property access or element access with the name of the symbol out to the writer. - * Uses the original source text for the name of the symbol if it is available to match how the user wrote the name, - * ensuring that any names written with literals use element accesses. - */ - function appendPropertyOrElementAccessForSymbol(symbol: Symbol, writer: SymbolWriter): void { - const symbolName = symbol.escapedName === InternalSymbolName.Default ? InternalSymbolName.Default : getNameOfSymbolAsWritten(symbol); - const firstChar = symbolName.charCodeAt(0); - const needsElementAccess = !isIdentifierStart(firstChar, languageVersion); - - if (needsElementAccess) { - if (firstChar !== CharacterCodes.openBracket) { - writePunctuation(writer, SyntaxKind.OpenBracketToken); - } - if (isSingleOrDoubleQuote(firstChar)) { - writer.writeStringLiteral(symbolName); - } - else { - writer.writeSymbol(symbolName, symbol); - } - if (firstChar !== CharacterCodes.openBracket) { - writePunctuation(writer, SyntaxKind.CloseBracketToken); - } - } - else { - writePunctuation(writer, SyntaxKind.DotToken); - writer.writeSymbol(symbolName, symbol); - } - } - - /** - * Enclosing declaration is optional when we don't want to get qualified name in the enclosing declaration scope - * Meaning needs to be specified if the enclosing declaration is given - */ - function buildSymbolDisplay(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags, typeFlags?: TypeFormatFlags): void { - let parentSymbol: Symbol; - function appendParentTypeArgumentsAndSymbolName(symbol: Symbol): void { - if (parentSymbol) { - // Write type arguments of instantiated class/interface here - if (flags & SymbolFormatFlags.WriteTypeParametersOrArguments) { - if (getCheckFlags(symbol) & CheckFlags.Instantiated) { - const params = getTypeParametersOfClassOrInterface(parentSymbol.flags & SymbolFlags.Alias ? resolveAlias(parentSymbol) : parentSymbol); - buildDisplayForTypeArgumentsAndDelimiters(params, (symbol).mapper, writer, enclosingDeclaration); - } - else { - buildTypeParameterDisplayFromSymbol(parentSymbol, writer, enclosingDeclaration); - } - } - appendPropertyOrElementAccessForSymbol(symbol, writer); - } - else { - appendSymbolNameOnly(symbol, writer); - } - parentSymbol = symbol; - } - - // Let the writer know we just wrote out a symbol. The declaration emitter writer uses - // this to determine if an import it has previously seen (and not written out) needs - // to be written to the file once the walk of the tree is complete. - // - // NOTE(cyrusn): This approach feels somewhat unfortunate. A simple pass over the tree - // up front (for example, during checking) could determine if we need to emit the imports - // and we could then access that data during declaration emit. - writer.trackSymbol(symbol, enclosingDeclaration, meaning); - /** @param endOfChain Set to false for recursive calls; non-recursive calls should always output something. */ - function walkSymbol(symbol: Symbol, meaning: SymbolFlags, endOfChain: boolean): void { - const accessibleSymbolChain = getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, !!(flags & SymbolFormatFlags.UseOnlyExternalAliasing)); - - if (!accessibleSymbolChain || - needsQualification(accessibleSymbolChain[0], enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) { - - // Go up and add our parent. - const parent = getParentOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol); - if (parent) { - walkSymbol(parent, getQualifiedLeftMeaning(meaning), /*endOfChain*/ false); - } - } - - if (accessibleSymbolChain) { - for (const accessibleSymbol of accessibleSymbolChain) { - appendParentTypeArgumentsAndSymbolName(accessibleSymbol); - } - } - else if ( - // If this is the last part of outputting the symbol, always output. The cases apply only to parent symbols. - endOfChain || - // If a parent symbol is an external module, don't write it. (We prefer just `x` vs `"foo/bar".x`.) - !(!parentSymbol && ts.forEach(symbol.declarations, hasExternalModuleSymbol)) && - // If a parent symbol is an anonymous type, don't write it. - !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral))) { - - appendParentTypeArgumentsAndSymbolName(symbol); - } - } - - // Get qualified name if the symbol is not a type parameter - // and there is an enclosing declaration or we specifically - // asked for it - const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; - const typeFormatFlag = TypeFormatFlags.UseFullyQualifiedType & typeFlags; - if (!isTypeParameter && (enclosingDeclaration || typeFormatFlag)) { - walkSymbol(symbol, meaning, /*endOfChain*/ true); - } - else { - appendParentTypeArgumentsAndSymbolName(symbol); - } - } - - function buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, globalFlags?: TypeFormatFlags, symbolStack?: Symbol[]) { - const globalFlagsToPass = globalFlags & (TypeFormatFlags.WriteOwnNameForAnyLike | TypeFormatFlags.WriteClassExpressionAsTypeLiteral); - let inObjectTypeLiteral = false; - return writeType(type, globalFlags); - - function writeType(type: Type, flags: TypeFormatFlags) { - const nextFlags = flags & ~TypeFormatFlags.InTypeAlias; - // Write undefined/null type as any - if (type.flags & TypeFlags.Intrinsic) { - // Special handling for unknown / resolving types, they should show up as any and not unknown or __resolving - writer.writeKeyword(!(globalFlags & TypeFormatFlags.WriteOwnNameForAnyLike) && isTypeAny(type) - ? "any" - : (type).intrinsicName); - } - else if (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType) { - if (inObjectTypeLiteral) { - writer.reportInaccessibleThisError(); - } - writer.writeKeyword("this"); - } - else if (getObjectFlags(type) & ObjectFlags.Reference) { - writeTypeReference(type, nextFlags); - } - else if (type.flags & TypeFlags.EnumLiteral && !(type.flags & TypeFlags.Union)) { - const parent = getParentOfSymbol(type.symbol); - buildSymbolDisplay(parent, writer, enclosingDeclaration, SymbolFlags.Type, SymbolFormatFlags.None, nextFlags); - // In a literal enum type with a single member E { A }, E and E.A denote the - // same type. We always display this type simply as E. - if (getDeclaredTypeOfSymbol(parent) !== type) { - writePunctuation(writer, SyntaxKind.DotToken); - appendSymbolNameOnly(type.symbol, writer); - } - } - else if (getObjectFlags(type) & ObjectFlags.ClassOrInterface || type.flags & (TypeFlags.EnumLike | TypeFlags.TypeParameter)) { - // The specified symbol flags need to be reinterpreted as type flags - buildSymbolDisplay(type.symbol, writer, enclosingDeclaration, SymbolFlags.Type, SymbolFormatFlags.None, nextFlags); - } - else if (!(flags & TypeFormatFlags.InTypeAlias) && type.aliasSymbol && - ((flags & TypeFormatFlags.UseAliasDefinedOutsideCurrentScope) || isTypeSymbolAccessible(type.aliasSymbol, enclosingDeclaration))) { - const typeArguments = type.aliasTypeArguments; - writeSymbolTypeReference(type.aliasSymbol, typeArguments, 0, length(typeArguments), nextFlags); - } - else if (type.flags & TypeFlags.UnionOrIntersection) { - writeUnionOrIntersectionType(type, nextFlags); - } - else if (getObjectFlags(type) & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { - writeAnonymousType(type, nextFlags); - } - else if (type.flags & TypeFlags.UniqueESSymbol) { - if (flags & TypeFormatFlags.AllowUniqueESSymbolType) { - writeKeyword(writer, SyntaxKind.UniqueKeyword); - writeSpace(writer); - } - else { - writer.reportInaccessibleUniqueSymbolError(); - } - writeKeyword(writer, SyntaxKind.SymbolKeyword); - } - else if (type.flags & TypeFlags.StringOrNumberLiteral) { - writer.writeStringLiteral(literalTypeToString(type)); - } - else if (type.flags & TypeFlags.Index) { - if (flags & TypeFormatFlags.InElementType) { - writePunctuation(writer, SyntaxKind.OpenParenToken); - } - writer.writeKeyword("keyof"); - writeSpace(writer); - writeType((type).type, TypeFormatFlags.InElementType); - if (flags & TypeFormatFlags.InElementType) { - writePunctuation(writer, SyntaxKind.CloseParenToken); - } - } - else if (type.flags & TypeFlags.IndexedAccess) { - writeType((type).objectType, TypeFormatFlags.InElementType); - writePunctuation(writer, SyntaxKind.OpenBracketToken); - writeType((type).indexType, TypeFormatFlags.None); - writePunctuation(writer, SyntaxKind.CloseBracketToken); - } - else { - // Should never get here - // { ... } - writePunctuation(writer, SyntaxKind.OpenBraceToken); - writeSpace(writer); - writePunctuation(writer, SyntaxKind.DotDotDotToken); - writeSpace(writer); - writePunctuation(writer, SyntaxKind.CloseBraceToken); - } - } - - - function writeTypeList(types: Type[], delimiter: SyntaxKind) { - for (let i = 0; i < types.length; i++) { - if (i > 0) { - if (delimiter !== SyntaxKind.CommaToken) { - writeSpace(writer); - } - writePunctuation(writer, delimiter); - writeSpace(writer); - } - writeType(types[i], delimiter === SyntaxKind.CommaToken ? TypeFormatFlags.None : TypeFormatFlags.InElementType); - } - } - - function writeSymbolTypeReference(symbol: Symbol, typeArguments: Type[], pos: number, end: number, flags: TypeFormatFlags) { - // Unnamed function expressions and arrow functions have reserved names that we don't want to display - if (symbol.flags & SymbolFlags.Class || !isReservedMemberName(symbol.escapedName)) { - buildSymbolDisplay(symbol, writer, enclosingDeclaration, SymbolFlags.Type, SymbolFormatFlags.None, flags); - } - if (pos < end) { - writePunctuation(writer, SyntaxKind.LessThanToken); - writeType(typeArguments[pos], TypeFormatFlags.InFirstTypeArgument); - pos++; - while (pos < end) { - writePunctuation(writer, SyntaxKind.CommaToken); - writeSpace(writer); - writeType(typeArguments[pos], TypeFormatFlags.None); - pos++; - } - writePunctuation(writer, SyntaxKind.GreaterThanToken); - } - } - - function writeTypeReference(type: TypeReference, flags: TypeFormatFlags) { - const typeArguments = type.typeArguments || emptyArray; - if (type.target === globalArrayType && !(flags & TypeFormatFlags.WriteArrayAsGenericType)) { - writeType(typeArguments[0], TypeFormatFlags.InElementType | TypeFormatFlags.InArrayType); - writePunctuation(writer, SyntaxKind.OpenBracketToken); - writePunctuation(writer, SyntaxKind.CloseBracketToken); - } - else if (type.target.objectFlags & ObjectFlags.Tuple) { - writePunctuation(writer, SyntaxKind.OpenBracketToken); - writeTypeList(type.typeArguments.slice(0, getTypeReferenceArity(type)), SyntaxKind.CommaToken); - writePunctuation(writer, SyntaxKind.CloseBracketToken); - } - else if (flags & TypeFormatFlags.WriteClassExpressionAsTypeLiteral && - type.symbol.valueDeclaration && - type.symbol.valueDeclaration.kind === SyntaxKind.ClassExpression) { - writeAnonymousType(type, flags); - } - else { - // Write the type reference in the format f.g.C where A and B are type arguments - // for outer type parameters, and f and g are the respective declaring containers of those - // type parameters. - const outerTypeParameters = type.target.outerTypeParameters; - let i = 0; - if (outerTypeParameters) { - const length = outerTypeParameters.length; - while (i < length) { - // Find group of type arguments for type parameters with the same declaring container. - const start = i; - const parent = getParentSymbolOfTypeParameter(outerTypeParameters[i]); - do { - i++; - } while (i < length && getParentSymbolOfTypeParameter(outerTypeParameters[i]) === parent); - // When type parameters are their own type arguments for the whole group (i.e. we have - // the default outer type arguments), we don't show the group. - if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { - writeSymbolTypeReference(parent, typeArguments, start, i, flags); - writePunctuation(writer, SyntaxKind.DotToken); - } - } - } - const typeParameterCount = (type.target.typeParameters || emptyArray).length; - writeSymbolTypeReference(type.symbol, typeArguments, i, typeParameterCount, flags); - } - } - - function writeUnionOrIntersectionType(type: UnionOrIntersectionType, flags: TypeFormatFlags) { - if (flags & TypeFormatFlags.InElementType) { - writePunctuation(writer, SyntaxKind.OpenParenToken); - } - if (type.flags & TypeFlags.Union) { - writeTypeList(formatUnionTypes(type.types), SyntaxKind.BarToken); - } - else { - writeTypeList(type.types, SyntaxKind.AmpersandToken); - } - if (flags & TypeFormatFlags.InElementType) { - writePunctuation(writer, SyntaxKind.CloseParenToken); - } - } - - function writeAnonymousType(type: ObjectType, flags: TypeFormatFlags) { - const symbol = type.symbol; - if (symbol) { - // Always use 'typeof T' for type of class, enum, and module objects - if (symbol.flags & SymbolFlags.Class && - !getBaseTypeVariableOfClass(symbol) && - !(symbol.valueDeclaration.kind === SyntaxKind.ClassExpression && flags & TypeFormatFlags.WriteClassExpressionAsTypeLiteral) || - symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule)) { - writeTypeOfSymbol(type.symbol, flags); - } - else if (shouldWriteTypeOfFunctionSymbol()) { - writeTypeOfSymbol(type.symbol, flags); - } - else if (contains(symbolStack, symbol)) { - // If type is an anonymous type literal in a type alias declaration, use type alias name - const typeAlias = getTypeAliasForTypeLiteral(type); - if (typeAlias) { - // The specified symbol flags need to be reinterpreted as type flags - buildSymbolDisplay(typeAlias, writer, enclosingDeclaration, SymbolFlags.Type, SymbolFormatFlags.None, flags); - } - else { - // Recursive usage, use any - writeKeyword(writer, SyntaxKind.AnyKeyword); - } - } - else { - // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead - // of types allows us to catch circular references to instantiations of the same anonymous type - // However, in case of class expressions, we want to write both the static side and the instance side. - // We skip adding the static side so that the instance side has a chance to be written - // before checking for circular references. - if (!symbolStack) { - symbolStack = []; - } - const isConstructorObject = type.objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class; - if (isConstructorObject) { - writeLiteralType(type, flags); - } - else { - symbolStack.push(symbol); - writeLiteralType(type, flags); - symbolStack.pop(); - } - } - } - else { - // Anonymous types with no symbol are never circular - writeLiteralType(type, flags); - } - - function shouldWriteTypeOfFunctionSymbol() { - const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method) && // typeof static method - some(symbol.declarations, declaration => hasModifier(declaration, ModifierFlags.Static)); - const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) && - (symbol.parent || // is exported function symbol - some(symbol.declarations, declaration => - declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); - if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { - // typeof is allowed only for static/non local functions - return !!(flags & TypeFormatFlags.UseTypeOfFunction) || // use typeof if format flags specify it - contains(symbolStack, symbol); // it is type of the symbol uses itself recursively - } - } - } - - function writeTypeOfSymbol(symbol: Symbol, typeFormatFlags?: TypeFormatFlags) { - if (typeFormatFlags & TypeFormatFlags.InArrayType) { - writePunctuation(writer, SyntaxKind.OpenParenToken); - } - writeKeyword(writer, SyntaxKind.TypeOfKeyword); - writeSpace(writer); - buildSymbolDisplay(symbol, writer, enclosingDeclaration, SymbolFlags.Value, SymbolFormatFlags.None, typeFormatFlags); - if (typeFormatFlags & TypeFormatFlags.InArrayType) { - writePunctuation(writer, SyntaxKind.CloseParenToken); - } - } - - function writePropertyWithModifiers(prop: Symbol) { - if (isReadonlySymbol(prop)) { - writeKeyword(writer, SyntaxKind.ReadonlyKeyword); - writeSpace(writer); - } - if (getCheckFlags(prop) & CheckFlags.Late) { - const decl = firstOrUndefined(prop.declarations); - const name = hasLateBindableName(decl) && resolveEntityName(decl.name.expression, SymbolFlags.Value); - if (name) { - writer.trackSymbol(name, enclosingDeclaration, SymbolFlags.Value); - } - } - buildSymbolDisplay(prop, writer); - if (prop.flags & SymbolFlags.Optional) { - writePunctuation(writer, SyntaxKind.QuestionToken); - } - } - - function shouldAddParenthesisAroundFunctionType(callSignature: Signature, flags: TypeFormatFlags) { - if (flags & TypeFormatFlags.InElementType) { - return true; - } - else if (flags & TypeFormatFlags.InFirstTypeArgument) { - // Add parenthesis around function type for the first type argument to avoid ambiguity - const typeParameters = callSignature.target && (flags & TypeFormatFlags.WriteTypeArgumentsOfSignature) ? - callSignature.target.typeParameters : callSignature.typeParameters; - return typeParameters && typeParameters.length !== 0; - } - return false; - } - - function writeLiteralType(type: ObjectType, flags: TypeFormatFlags) { - if (isGenericMappedType(type)) { - writeMappedType(type); - return; - } - - const resolved = resolveStructuredTypeMembers(type); - if (!resolved.properties.length && !resolved.stringIndexInfo && !resolved.numberIndexInfo) { - if (!resolved.callSignatures.length && !resolved.constructSignatures.length) { - writePunctuation(writer, SyntaxKind.OpenBraceToken); - writePunctuation(writer, SyntaxKind.CloseBraceToken); - return; - } - - if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { - const parenthesizeSignature = shouldAddParenthesisAroundFunctionType(resolved.callSignatures[0], flags); - if (parenthesizeSignature) { - writePunctuation(writer, SyntaxKind.OpenParenToken); - } - buildSignatureDisplay(resolved.callSignatures[0], writer, enclosingDeclaration, globalFlagsToPass | TypeFormatFlags.WriteArrowStyleSignature, /*kind*/ undefined, symbolStack); - if (parenthesizeSignature) { - writePunctuation(writer, SyntaxKind.CloseParenToken); - } - return; - } - if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { - if (flags & TypeFormatFlags.InElementType) { - writePunctuation(writer, SyntaxKind.OpenParenToken); - } - writeKeyword(writer, SyntaxKind.NewKeyword); - writeSpace(writer); - buildSignatureDisplay(resolved.constructSignatures[0], writer, enclosingDeclaration, globalFlagsToPass | TypeFormatFlags.WriteArrowStyleSignature, /*kind*/ undefined, symbolStack); - if (flags & TypeFormatFlags.InElementType) { - writePunctuation(writer, SyntaxKind.CloseParenToken); - } - return; - } - } - - const saveInObjectTypeLiteral = inObjectTypeLiteral; - inObjectTypeLiteral = true; - writePunctuation(writer, SyntaxKind.OpenBraceToken); - writer.writeLine(); - writer.increaseIndent(); - writeObjectLiteralType(resolved); - writer.decreaseIndent(); - writePunctuation(writer, SyntaxKind.CloseBraceToken); - inObjectTypeLiteral = saveInObjectTypeLiteral; - } - - function writeObjectLiteralType(resolved: ResolvedType) { - for (const signature of resolved.callSignatures) { - buildSignatureDisplay(signature, writer, enclosingDeclaration, globalFlagsToPass, /*kind*/ undefined, symbolStack); - writePunctuation(writer, SyntaxKind.SemicolonToken); - writer.writeLine(); - } - for (const signature of resolved.constructSignatures) { - buildSignatureDisplay(signature, writer, enclosingDeclaration, globalFlagsToPass, SignatureKind.Construct, symbolStack); - writePunctuation(writer, SyntaxKind.SemicolonToken); - writer.writeLine(); - } - const stringIndexInfo = resolved.objectFlags & ObjectFlags.ReverseMapped && resolved.stringIndexInfo ? - createIndexInfo(anyType, resolved.stringIndexInfo.isReadonly, resolved.stringIndexInfo.declaration) : - resolved.stringIndexInfo; - buildIndexSignatureDisplay(stringIndexInfo, writer, IndexKind.String, enclosingDeclaration, globalFlags, symbolStack); - buildIndexSignatureDisplay(resolved.numberIndexInfo, writer, IndexKind.Number, enclosingDeclaration, globalFlags, symbolStack); - for (const p of resolved.properties) { - if (globalFlags & TypeFormatFlags.WriteClassExpressionAsTypeLiteral) { - if (p.flags & SymbolFlags.Prototype) { - continue; - } - if (getDeclarationModifierFlagsFromSymbol(p) & (ModifierFlags.Private | ModifierFlags.Protected)) { - writer.reportPrivateInBaseOfClassExpression(symbolName(p)); - } - } - const t = getCheckFlags(p) & CheckFlags.ReverseMapped ? anyType : getTypeOfSymbol(p); - if (p.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(t).length) { - const signatures = getSignaturesOfType(t, SignatureKind.Call); - for (const signature of signatures) { - writePropertyWithModifiers(p); - buildSignatureDisplay(signature, writer, enclosingDeclaration, globalFlagsToPass, /*kind*/ undefined, symbolStack); - writePunctuation(writer, SyntaxKind.SemicolonToken); - writer.writeLine(); - } - } - else { - writePropertyWithModifiers(p); - writePunctuation(writer, SyntaxKind.ColonToken); - writeSpace(writer); - writeType(t, globalFlags & TypeFormatFlags.WriteClassExpressionAsTypeLiteral); - writePunctuation(writer, SyntaxKind.SemicolonToken); - writer.writeLine(); - } - } - } - - function writeMappedType(type: MappedType) { - writePunctuation(writer, SyntaxKind.OpenBraceToken); - writer.writeLine(); - writer.increaseIndent(); - if (type.declaration.readonlyToken) { - writeKeyword(writer, SyntaxKind.ReadonlyKeyword); - writeSpace(writer); - } - writePunctuation(writer, SyntaxKind.OpenBracketToken); - appendSymbolNameOnly(getTypeParameterFromMappedType(type).symbol, writer); - writeSpace(writer); - writeKeyword(writer, SyntaxKind.InKeyword); - writeSpace(writer); - writeType(getConstraintTypeFromMappedType(type), TypeFormatFlags.None); - writePunctuation(writer, SyntaxKind.CloseBracketToken); - if (type.declaration.questionToken) { - writePunctuation(writer, SyntaxKind.QuestionToken); - } - writePunctuation(writer, SyntaxKind.ColonToken); - writeSpace(writer); - writeType(getTemplateTypeFromMappedType(type), TypeFormatFlags.None); - writePunctuation(writer, SyntaxKind.SemicolonToken); - writer.writeLine(); - writer.decreaseIndent(); - writePunctuation(writer, SyntaxKind.CloseBraceToken); - } - } - - function buildTypeParameterDisplayFromSymbol(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags) { - const targetSymbol = getTargetSymbol(symbol); - if (targetSymbol.flags & SymbolFlags.Class || targetSymbol.flags & SymbolFlags.Interface || targetSymbol.flags & SymbolFlags.TypeAlias) { - buildDisplayForTypeParametersAndDelimiters(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol), writer, enclosingDeclaration, flags); - } - } - - function buildTypeParameterDisplay(tp: TypeParameter, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, symbolStack?: Symbol[]) { - appendSymbolNameOnly(tp.symbol, writer); - const constraint = getConstraintOfTypeParameter(tp); - if (constraint) { - writeSpace(writer); - writeKeyword(writer, SyntaxKind.ExtendsKeyword); - writeSpace(writer); - buildTypeDisplay(constraint, writer, enclosingDeclaration, flags, symbolStack); - } - const defaultType = getDefaultFromTypeParameter(tp); - if (defaultType) { - writeSpace(writer); - writePunctuation(writer, SyntaxKind.EqualsToken); - writeSpace(writer); - buildTypeDisplay(defaultType, writer, enclosingDeclaration, flags, symbolStack); - } - } - - function buildParameterDisplay(p: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, symbolStack?: Symbol[]) { - const parameterNode = p.valueDeclaration; - - if (parameterNode ? isRestParameter(parameterNode) : isTransientSymbol(p) && p.isRestParameter) { - writePunctuation(writer, SyntaxKind.DotDotDotToken); - } - if (parameterNode && isBindingPattern(parameterNode.name)) { - buildBindingPatternDisplay(parameterNode.name, writer, enclosingDeclaration, flags, symbolStack); - } - else { - appendSymbolNameOnly(p, writer); - } - if (parameterNode && isOptionalParameter(parameterNode)) { - writePunctuation(writer, SyntaxKind.QuestionToken); - } - writePunctuation(writer, SyntaxKind.ColonToken); - writeSpace(writer); - - let type = getTypeOfSymbol(p); - if (parameterNode && isRequiredInitializedParameter(parameterNode)) { - type = getOptionalType(type); - } - buildTypeDisplay(type, writer, enclosingDeclaration, flags, symbolStack); - } - - function buildBindingPatternDisplay(bindingPattern: BindingPattern, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, symbolStack?: Symbol[]) { - // We have to explicitly emit square bracket and bracket because these tokens are not stored inside the node. - if (bindingPattern.kind === SyntaxKind.ObjectBindingPattern) { - writePunctuation(writer, SyntaxKind.OpenBraceToken); - buildDisplayForCommaSeparatedList(bindingPattern.elements, writer, e => buildBindingElementDisplay(e, writer, enclosingDeclaration, flags, symbolStack)); - writePunctuation(writer, SyntaxKind.CloseBraceToken); - } - else if (bindingPattern.kind === SyntaxKind.ArrayBindingPattern) { - writePunctuation(writer, SyntaxKind.OpenBracketToken); - const elements = bindingPattern.elements; - buildDisplayForCommaSeparatedList(elements, writer, e => buildBindingElementDisplay(e, writer, enclosingDeclaration, flags, symbolStack)); - if (elements && elements.hasTrailingComma) { - writePunctuation(writer, SyntaxKind.CommaToken); - } - writePunctuation(writer, SyntaxKind.CloseBracketToken); - } - } - - function buildBindingElementDisplay(bindingElement: ArrayBindingElement, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, symbolStack?: Symbol[]) { - if (isOmittedExpression(bindingElement)) { - return; - } - Debug.assert(bindingElement.kind === SyntaxKind.BindingElement); - if (bindingElement.propertyName) { - writer.writeProperty(getTextOfNode(bindingElement.propertyName)); - writePunctuation(writer, SyntaxKind.ColonToken); - writeSpace(writer); - } - if (isBindingPattern(bindingElement.name)) { - buildBindingPatternDisplay(bindingElement.name, writer, enclosingDeclaration, flags, symbolStack); - } - else { - if (bindingElement.dotDotDotToken) { - writePunctuation(writer, SyntaxKind.DotDotDotToken); - } - appendSymbolNameOnly(bindingElement.symbol, writer); - } - } - - function buildDisplayForTypeParametersAndDelimiters(typeParameters: ReadonlyArray, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, symbolStack?: Symbol[]) { - if (typeParameters && typeParameters.length) { - writePunctuation(writer, SyntaxKind.LessThanToken); - buildDisplayForCommaSeparatedList(typeParameters, writer, p => buildTypeParameterDisplay(p, writer, enclosingDeclaration, flags, symbolStack)); - writePunctuation(writer, SyntaxKind.GreaterThanToken); - } - } - - function buildDisplayForCommaSeparatedList(list: ReadonlyArray, writer: SymbolWriter, action: (item: T) => void) { - for (let i = 0; i < list.length; i++) { - if (i > 0) { - writePunctuation(writer, SyntaxKind.CommaToken); - writeSpace(writer); - } - action(list[i]); - } - } - - function buildDisplayForTypeArgumentsAndDelimiters(typeParameters: ReadonlyArray, mapper: TypeMapper, writer: SymbolWriter, enclosingDeclaration?: Node) { - if (typeParameters && typeParameters.length) { - writePunctuation(writer, SyntaxKind.LessThanToken); - let flags = TypeFormatFlags.InFirstTypeArgument; - for (let i = 0; i < typeParameters.length; i++) { - if (i > 0) { - writePunctuation(writer, SyntaxKind.CommaToken); - writeSpace(writer); - flags = TypeFormatFlags.None; - } - buildTypeDisplay(mapper(typeParameters[i]), writer, enclosingDeclaration, flags); - } - writePunctuation(writer, SyntaxKind.GreaterThanToken); - } - } - - function buildDisplayForParametersAndDelimiters(thisParameter: Symbol | undefined, parameters: Symbol[], writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, symbolStack?: Symbol[]) { - writePunctuation(writer, SyntaxKind.OpenParenToken); - if (thisParameter) { - buildParameterDisplay(thisParameter, writer, enclosingDeclaration, flags, symbolStack); - } - for (let i = 0; i < parameters.length; i++) { - if (i > 0 || thisParameter) { - writePunctuation(writer, SyntaxKind.CommaToken); - writeSpace(writer); - } - buildParameterDisplay(parameters[i], writer, enclosingDeclaration, flags, symbolStack); - } - writePunctuation(writer, SyntaxKind.CloseParenToken); - } - - function buildTypePredicateDisplay(predicate: TypePredicate, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, symbolStack?: Symbol[]): void { - if (isIdentifierTypePredicate(predicate)) { - writer.writeParameter(predicate.parameterName); - } - else { - writeKeyword(writer, SyntaxKind.ThisKeyword); - } - writeSpace(writer); - writeKeyword(writer, SyntaxKind.IsKeyword); - writeSpace(writer); - buildTypeDisplay(predicate.type, writer, enclosingDeclaration, flags, symbolStack); - } - - function buildReturnTypeDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, symbolStack?: Symbol[]) { - const returnType = getReturnTypeOfSignature(signature); - if (flags & TypeFormatFlags.SuppressAnyReturnType && isTypeAny(returnType)) { - return; - } - - if (flags & TypeFormatFlags.WriteArrowStyleSignature) { - writeSpace(writer); - writePunctuation(writer, SyntaxKind.EqualsGreaterThanToken); - } - else { - writePunctuation(writer, SyntaxKind.ColonToken); - } - writeSpace(writer); - - const typePredicate = getTypePredicateOfSignature(signature); - if (typePredicate) { - buildTypePredicateDisplay(typePredicate, writer, enclosingDeclaration, flags, symbolStack); - } - else { - buildTypeDisplay(returnType, writer, enclosingDeclaration, flags, symbolStack); - } - } - - function buildSignatureDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind, symbolStack?: Symbol[]) { - if (kind === SignatureKind.Construct) { - writeKeyword(writer, SyntaxKind.NewKeyword); - writeSpace(writer); - } - - if (signature.target && (flags & TypeFormatFlags.WriteTypeArgumentsOfSignature)) { - // Instantiated signature, write type arguments instead - // This is achieved by passing in the mapper separately - buildDisplayForTypeArgumentsAndDelimiters(signature.target.typeParameters, signature.mapper, writer, enclosingDeclaration); - } - else { - buildDisplayForTypeParametersAndDelimiters(signature.typeParameters, writer, enclosingDeclaration, flags, symbolStack); - } - - buildDisplayForParametersAndDelimiters(signature.thisParameter, signature.parameters, writer, enclosingDeclaration, flags, symbolStack); - - buildReturnTypeDisplay(signature, writer, enclosingDeclaration, flags, symbolStack); - } - - function buildIndexSignatureDisplay(info: IndexInfo, writer: SymbolWriter, kind: IndexKind, enclosingDeclaration?: Node, globalFlags?: TypeFormatFlags, symbolStack?: Symbol[]) { - if (info) { - if (info.isReadonly) { - writeKeyword(writer, SyntaxKind.ReadonlyKeyword); - writeSpace(writer); - } - writePunctuation(writer, SyntaxKind.OpenBracketToken); - writer.writeParameter(info.declaration ? declarationNameToString(info.declaration.parameters[0].name) : "x"); - writePunctuation(writer, SyntaxKind.ColonToken); - writeSpace(writer); - switch (kind) { - case IndexKind.Number: - writeKeyword(writer, SyntaxKind.NumberKeyword); - break; - case IndexKind.String: - writeKeyword(writer, SyntaxKind.StringKeyword); - break; - } - - writePunctuation(writer, SyntaxKind.CloseBracketToken); - writePunctuation(writer, SyntaxKind.ColonToken); - writeSpace(writer); - if (info.type) { - buildTypeDisplay(info.type, writer, enclosingDeclaration, globalFlags, symbolStack); - } - else { - writeKeyword(writer, SyntaxKind.AnyKeyword); - } - writePunctuation(writer, SyntaxKind.SemicolonToken); - writer.writeLine(); - } - } - - return _displayBuilder || (_displayBuilder = { - buildSymbolDisplay, - buildTypeDisplay, - buildTypeParameterDisplay, - buildTypePredicateDisplay, - buildParameterDisplay, - buildDisplayForParametersAndDelimiters, - buildDisplayForTypeParametersAndDelimiters, - buildTypeParameterDisplayFromSymbol, - buildSignatureDisplay, - buildIndexSignatureDisplay, - buildReturnTypeDisplay - }); - } - function isDeclarationVisible(node: Declaration): boolean { if (node) { const links = getNodeLinks(node); @@ -25348,7 +24879,7 @@ namespace ts { } } - function writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter) { + function writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: EmitTextWriter) { // Get type of the symbol if this is the valid symbol otherwise get type at location const symbol = getSymbolOfNode(declaration); let type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature)) @@ -25361,18 +24892,17 @@ namespace ts { if (flags & TypeFormatFlags.AddUndefined) { type = getOptionalType(type); } - - getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration, flags); + typeToString(type, enclosingDeclaration, flags | TypeFormatFlags.MultilineObjectLiterals, writer); } - function writeReturnTypeOfSignatureDeclaration(signatureDeclaration: SignatureDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter) { + function writeReturnTypeOfSignatureDeclaration(signatureDeclaration: SignatureDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: EmitTextWriter) { const signature = getSignatureFromDeclaration(signatureDeclaration); - getSymbolDisplayBuilder().buildTypeDisplay(getReturnTypeOfSignature(signature), writer, enclosingDeclaration, flags); + typeToString(getReturnTypeOfSignature(signature), enclosingDeclaration, flags | TypeFormatFlags.MultilineObjectLiterals, writer); } - function writeTypeOfExpression(expr: Expression, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter) { + function writeTypeOfExpression(expr: Expression, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: EmitTextWriter) { const type = getWidenedType(getRegularTypeOfExpression(expr)); - getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration, flags); + typeToString(type, enclosingDeclaration, flags | TypeFormatFlags.MultilineObjectLiterals, writer); } function hasGlobalName(name: string): boolean { @@ -25420,7 +24950,7 @@ namespace ts { return false; } - function writeLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, writer: SymbolWriter) { + function writeLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, writer: EmitTextWriter) { const type = getTypeOfSymbol(getSymbolOfNode(node)); writer.writeStringLiteral(literalTypeToString(type)); } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 24c2153c582..0faabbda2a0 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2771,10 +2771,10 @@ namespace ts { function Signature() {} // tslint:disable-line no-empty function Node(this: Node, kind: SyntaxKind, pos: number, end: number) { - this.id = 0; - this.kind = kind; this.pos = pos; this.end = end; + this.kind = kind; + this.id = 0; this.flags = NodeFlags.None; this.modifierFlagsCache = ModifierFlags.None; this.transformFlags = TransformFlags.None; @@ -3046,6 +3046,10 @@ namespace ts { return (arg: T) => f(arg) && g(arg); } + export function or(f: (arg: T) => boolean, g: (arg: T) => boolean) { + return (arg: T) => f(arg) || g(arg); + } + export function assertTypeIsNever(_: never): void { } // tslint:disable-line no-empty export interface FileAndDirectoryExistence { diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index b85c79e775c..f37ae497f84 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -20,14 +20,14 @@ namespace ts { type GetSymbolAccessibilityDiagnostic = (symbolAccessibilityResult: SymbolAccessibilityResult) => SymbolAccessibilityDiagnostic; - interface EmitTextWriterWithSymbolWriter extends EmitTextWriter, SymbolWriter { + interface EmitTextWriterWithSymbolWriter extends EmitTextWriter { getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic; } interface SymbolAccessibilityDiagnostic { errorNode: Node; diagnosticMessage: DiagnosticMessage; - typeName?: DeclarationName; + typeName?: DeclarationName | QualifiedName; } export function getDeclarationDiagnostics(host: EmitHost, resolver: EmitResolver, targetSourceFile: SourceFile): Diagnostic[] { @@ -358,7 +358,7 @@ namespace ts { } else { errorNameNode = declaration.name; - const format = TypeFormatFlags.UseTypeOfFunction | + const format = TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.WriteDefaultSymbolWithoutName | TypeFormatFlags.WriteClassExpressionAsTypeLiteral | (shouldUseResolverType ? TypeFormatFlags.AddUndefined : 0); resolver.writeTypeOfDeclaration(declaration, enclosingDeclaration, format, writer); @@ -378,7 +378,7 @@ namespace ts { resolver.writeReturnTypeOfSignatureDeclaration( signature, enclosingDeclaration, - TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.WriteClassExpressionAsTypeLiteral, + TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.WriteClassExpressionAsTypeLiteral | TypeFormatFlags.WriteDefaultSymbolWithoutName, writer); errorNameNode = undefined; } @@ -643,7 +643,7 @@ namespace ts { resolver.writeTypeOfExpression( expr, enclosingDeclaration, - TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.WriteClassExpressionAsTypeLiteral, + TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.WriteClassExpressionAsTypeLiteral | TypeFormatFlags.WriteDefaultSymbolWithoutName, writer); write(";"); writeLine(); diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index aee8c98db0d..8a1de17826c 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -5,7 +5,6 @@ /// namespace ts { - const delimiters = createDelimiterMap(); const brackets = createBracketsMap(); /*@internal*/ @@ -200,7 +199,7 @@ namespace ts { // Reset state sourceMap.reset(); - writer.reset(); + writer.clear(); currentSourceFile = undefined; bundledHelpers = undefined; @@ -292,16 +291,27 @@ namespace ts { let tempFlags: TempFlags; // TempFlags for the current name generation scope. let writer: EmitTextWriter; let ownWriter: EmitTextWriter; + let write = writeBase; + let commitPendingSemicolon: typeof commitPendingSemicolonInternal = noop; + let writeSemicolon: typeof writeSemicolonInternal = writeSemicolonInternal; + let pendingSemicolon = false; + if (printerOptions.omitTrailingSemicolon) { + commitPendingSemicolon = commitPendingSemicolonInternal; + writeSemicolon = deferWriteSemicolon; + } + const syntheticParent: TextRange = { pos: -1, end: -1 }; reset(); return { // public API printNode, + printList, printFile, printBundle, // internal API writeNode, + writeList, writeFile, writeBundle }; @@ -326,6 +336,11 @@ namespace ts { return endPrint(); } + function printList(format: ListFormat, nodes: NodeArray, sourceFile: SourceFile) { + writeList(format, nodes, sourceFile, beginPrint()); + return endPrint(); + } + function printBundle(bundle: Bundle): string { writeBundle(bundle, beginPrint()); return endPrint(); @@ -349,6 +364,17 @@ namespace ts { writer = previousWriter; } + function writeList(format: ListFormat, nodes: NodeArray, sourceFile: SourceFile | undefined, output: EmitTextWriter) { + const previousWriter = writer; + setWriter(output); + if (sourceFile) { + setSourceFile(sourceFile); + } + emitList(syntheticParent, nodes, format); + reset(); + writer = previousWriter; + } + function writeBundle(bundle: Bundle, output: EmitTextWriter) { const previousWriter = writer; setWriter(output); @@ -378,7 +404,7 @@ namespace ts { function endPrint() { const text = ownWriter.getText(); - ownWriter.reset(); + ownWriter.clear(); return text; } @@ -482,7 +508,9 @@ namespace ts { function emitMappedTypeParameter(node: TypeParameterDeclaration): void { emit(node.name); - write(" in "); + writeSpace(); + writeKeyword("in"); + writeSpace(); emit(node.constraint); } @@ -493,7 +521,7 @@ namespace ts { // Strict mode reserved words // Contextual keywords if (isKeyword(kind)) { - writeTokenNode(node); + writeTokenNode(node, writeKeyword); return; } @@ -752,7 +780,7 @@ namespace ts { } if (isToken(node)) { - writeTokenNode(node); + writeTokenNode(node, writePunctuation); return; } } @@ -780,7 +808,7 @@ namespace ts { case SyntaxKind.TrueKeyword: case SyntaxKind.ThisKeyword: case SyntaxKind.ImportKeyword: - writeTokenNode(node); + writeTokenNode(node, writeKeyword); return; // Expressions @@ -885,10 +913,11 @@ namespace ts { const text = getLiteralTextOfNode(node); if ((printerOptions.sourceMap || printerOptions.inlineSourceMap) && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) { - writer.writeLiteral(text); + writeLiteral(text); } else { - write(text); + // Quick info expects all literals to be called with writeStringLiteral, as there's no specific type for numberLiterals + writeStringLiteral(text); } } @@ -897,8 +926,9 @@ namespace ts { // function emitIdentifier(node: Identifier) { - write(getTextOfNode(node, /*includeTrivia*/ false)); - emitTypeArguments(node, node.typeArguments); + const writeText = node.symbol ? writeSymbol : write; + writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol); + emitList(node, node.typeArguments, ListFormat.TypeParameters); // Call emitList directly since it could be an array of TypeParameterDeclarations _or_ type arguments } // @@ -907,7 +937,7 @@ namespace ts { function emitQualifiedName(node: QualifiedName) { emitEntityName(node.left); - write("."); + writePunctuation("."); emit(node.right); } @@ -921,9 +951,9 @@ namespace ts { } function emitComputedPropertyName(node: ComputedPropertyName) { - write("["); + writePunctuation("["); emitExpression(node.expression); - write("]"); + writePunctuation("]"); } // @@ -932,8 +962,18 @@ namespace ts { function emitTypeParameter(node: TypeParameterDeclaration) { emit(node.name); - emitWithPrefix(" extends ", node.constraint); - emitWithPrefix(" = ", node.default); + if (node.constraint) { + writeSpace(); + writeKeyword("extends"); + writeSpace(); + emit(node.constraint); + } + if (node.default) { + writeSpace(); + writeOperator("="); + writeSpace(); + emit(node.default); + } } function emitParameter(node: ParameterDeclaration) { @@ -941,20 +981,20 @@ namespace ts { emitModifiers(node, node.modifiers); emitIfPresent(node.dotDotDotToken); if (node.name) { - emit(node.name); + emitNodeWithWriter(node.name, writeParameter); } emitIfPresent(node.questionToken); if (node.parent && node.parent.kind === SyntaxKind.JSDocFunctionType && !node.name) { emit(node.type); } else { - emitWithPrefix(": ", node.type); + emitTypeAnnotation(node.type); } - emitExpressionWithPrefix(" = ", node.initializer); + emitInitializer(node.initializer); } function emitDecorator(decorator: Decorator) { - write("@"); + writePunctuation("@"); emitExpression(decorator.expression); } @@ -965,10 +1005,10 @@ namespace ts { function emitPropertySignature(node: PropertySignature) { emitDecorators(node, node.decorators); emitModifiers(node, node.modifiers); - emit(node.name); + emitNodeWithWriter(node.name, writeProperty); emitIfPresent(node.questionToken); - emitWithPrefix(": ", node.type); - write(";"); + emitTypeAnnotation(node.type); + writeSemicolon(); } function emitPropertyDeclaration(node: PropertyDeclaration) { @@ -976,9 +1016,9 @@ namespace ts { emitModifiers(node, node.modifiers); emit(node.name); emitIfPresent(node.questionToken); - emitWithPrefix(": ", node.type); - emitExpressionWithPrefix(" = ", node.initializer); - write(";"); + emitTypeAnnotation(node.type); + emitInitializer(node.initializer); + writeSemicolon(); } function emitMethodSignature(node: MethodSignature) { @@ -988,8 +1028,8 @@ namespace ts { emitIfPresent(node.questionToken); emitTypeParameters(node, node.typeParameters); emitParameters(node, node.parameters); - emitWithPrefix(": ", node.type); - write(";"); + emitTypeAnnotation(node.type); + writeSemicolon(); } function emitMethodDeclaration(node: MethodDeclaration) { @@ -1003,14 +1043,15 @@ namespace ts { function emitConstructor(node: ConstructorDeclaration) { emitModifiers(node, node.modifiers); - write("constructor"); + writeKeyword("constructor"); emitSignatureAndBody(node, emitSignatureHead); } function emitAccessorDeclaration(node: AccessorDeclaration) { emitDecorators(node, node.decorators); emitModifiers(node, node.modifiers); - write(node.kind === SyntaxKind.GetAccessor ? "get " : "set "); + writeKeyword(node.kind === SyntaxKind.GetAccessor ? "get" : "set"); + writeSpace(); emit(node.name); emitSignatureAndBody(node, emitSignatureHead); } @@ -1020,30 +1061,31 @@ namespace ts { emitModifiers(node, node.modifiers); emitTypeParameters(node, node.typeParameters); emitParameters(node, node.parameters); - emitWithPrefix(": ", node.type); - write(";"); + emitTypeAnnotation(node.type); + writeSemicolon(); } function emitConstructSignature(node: ConstructSignatureDeclaration) { emitDecorators(node, node.decorators); emitModifiers(node, node.modifiers); - write("new "); + writeKeyword("new"); + writeSpace(); emitTypeParameters(node, node.typeParameters); emitParameters(node, node.parameters); - emitWithPrefix(": ", node.type); - write(";"); + emitTypeAnnotation(node.type); + writeSemicolon(); } function emitIndexSignature(node: IndexSignatureDeclaration) { emitDecorators(node, node.decorators); emitModifiers(node, node.modifiers); emitParametersForIndexSignature(node, node.parameters); - emitWithPrefix(": ", node.type); - write(";"); + emitTypeAnnotation(node.type); + writeSemicolon(); } function emitSemicolonClassElement() { - write(";"); + writeSemicolon(); } // @@ -1052,7 +1094,9 @@ namespace ts { function emitTypePredicate(node: TypePredicateNode) { emit(node.parameterName); - write(" is "); + writeSpace(); + writeKeyword("is"); + writeSpace(); emit(node.type); } @@ -1064,7 +1108,9 @@ namespace ts { function emitFunctionType(node: FunctionTypeNode) { emitTypeParameters(node, node.typeParameters); emitParametersForArrow(node, node.parameters); - write(" => "); + writeSpace(); + writePunctuation("=>"); + writeSpace(); emit(node.type); } @@ -1092,28 +1138,33 @@ namespace ts { } function emitConstructorType(node: ConstructorTypeNode) { - write("new "); + writeKeyword("new"); + writeSpace(); emitTypeParameters(node, node.typeParameters); emitParameters(node, node.parameters); - write(" => "); + writeSpace(); + writePunctuation("=>"); + writeSpace(); emit(node.type); } function emitTypeQuery(node: TypeQueryNode) { - write("typeof "); + writeKeyword("typeof"); + writeSpace(); emit(node.exprName); } function emitTypeLiteral(node: TypeLiteralNode) { - write("{"); + writePunctuation("{"); const flags = getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineTypeLiteralMembers : ListFormat.MultiLineTypeLiteralMembers; emitList(node, node.members, flags | ListFormat.NoSpaceIfEmpty); - write("}"); + writePunctuation("}"); } function emitArrayType(node: ArrayTypeNode) { emit(node.elementType); - write("[]"); + writePunctuation("["); + writePunctuation("]"); } function emitJSDocVariadicType(node: JSDocVariadicType) { @@ -1122,9 +1173,9 @@ namespace ts { } function emitTupleType(node: TupleTypeNode) { - write("["); + writePunctuation("["); emitList(node, node.elementTypes, ListFormat.TupleTypeElements); - write("]"); + writePunctuation("]"); } function emitUnionType(node: UnionTypeNode) { @@ -1136,33 +1187,33 @@ namespace ts { } function emitParenthesizedType(node: ParenthesizedTypeNode) { - write("("); + writePunctuation("("); emit(node.type); - write(")"); + writePunctuation(")"); } function emitThisType() { - write("this"); + writeKeyword("this"); } function emitTypeOperator(node: TypeOperatorNode) { - writeTokenText(node.operator); - write(" "); + writeTokenText(node.operator, writeKeyword); + writeSpace(); emit(node.type); } function emitIndexedAccessType(node: IndexedAccessTypeNode) { emit(node.objectType); - write("["); + writePunctuation("["); emit(node.indexType); - write("]"); + writePunctuation("]"); } function emitMappedType(node: MappedTypeNode) { const emitFlags = getEmitFlags(node); - write("{"); + writePunctuation("{"); if (emitFlags & EmitFlags.SingleLine) { - write(" "); + writeSpace(); } else { writeLine(); @@ -1170,25 +1221,26 @@ namespace ts { } if (node.readonlyToken) { emit(node.readonlyToken); - write(" "); + writeSpace(); } - write("["); + writePunctuation("["); pipelineEmitWithNotification(EmitHint.MappedTypeParameter, node.typeParameter); - write("]"); + writePunctuation("]"); emitIfPresent(node.questionToken); - write(": "); + writePunctuation(":"); + writeSpace(); emit(node.type); - write(";"); + writeSemicolon(); if (emitFlags & EmitFlags.SingleLine) { - write(" "); + writeSpace(); } else { writeLine(); decreaseIndent(); } - write("}"); + writePunctuation("}"); } function emitLiteralType(node: LiteralTypeNode) { @@ -1200,22 +1252,26 @@ namespace ts { // function emitObjectBindingPattern(node: ObjectBindingPattern) { - write("{"); + writePunctuation("{"); emitList(node, node.elements, ListFormat.ObjectBindingPatternElements); - write("}"); + writePunctuation("}"); } function emitArrayBindingPattern(node: ArrayBindingPattern) { - write("["); + writePunctuation("["); emitList(node, node.elements, ListFormat.ArrayBindingPatternElements); - write("]"); + writePunctuation("]"); } function emitBindingElement(node: BindingElement) { - emitWithSuffix(node.propertyName, ": "); + if (node.propertyName) { + emit(node.propertyName); + writePunctuation(":"); + writeSpace(); + } emitIfPresent(node.dotDotDotToken); emit(node.name); - emitExpressionWithPrefix(" = ", node.initializer); + emitInitializer(node.initializer); } // @@ -1260,7 +1316,7 @@ namespace ts { increaseIndentIf(indentBeforeDot); const shouldEmitDotDot = !indentBeforeDot && needsDotDotForPropertyAccess(node.expression); - write(shouldEmitDotDot ? ".." : "."); + writePunctuation(shouldEmitDotDot ? ".." : "."); increaseIndentIf(indentAfterDot); emit(node.name); @@ -1289,9 +1345,9 @@ namespace ts { function emitElementAccessExpression(node: ElementAccessExpression) { emitExpression(node.expression); - write("["); + writePunctuation("["); emitExpression(node.argumentExpression); - write("]"); + writePunctuation("]"); } function emitCallExpression(node: CallExpression) { @@ -1301,7 +1357,8 @@ namespace ts { } function emitNewExpression(node: NewExpression) { - write("new "); + writeKeyword("new"); + writeSpace(); emitExpression(node.expression); emitTypeArguments(node, node.typeArguments); emitExpressionList(node, node.arguments, ListFormat.NewExpressionArguments); @@ -1309,21 +1366,21 @@ namespace ts { function emitTaggedTemplateExpression(node: TaggedTemplateExpression) { emitExpression(node.tag); - write(" "); + writeSpace(); emitExpression(node.template); } function emitTypeAssertionExpression(node: TypeAssertion) { - write("<"); + writePunctuation("<"); emit(node.type); - write(">"); + writePunctuation(">"); emitExpression(node.expression); } function emitParenthesizedExpression(node: ParenthesizedExpression) { - write("("); + writePunctuation("("); emitExpression(node.expression); - write(")"); + writePunctuation(")"); } function emitFunctionExpression(node: FunctionExpression) { @@ -1339,35 +1396,39 @@ namespace ts { function emitArrowFunctionHead(node: ArrowFunction) { emitTypeParameters(node, node.typeParameters); emitParametersForArrow(node, node.parameters); - emitWithPrefix(": ", node.type); - write(" "); + emitTypeAnnotation(node.type); + writeSpace(); emit(node.equalsGreaterThanToken); } function emitDeleteExpression(node: DeleteExpression) { - write("delete "); + writeKeyword("delete"); + writeSpace(); emitExpression(node.expression); } function emitTypeOfExpression(node: TypeOfExpression) { - write("typeof "); + writeKeyword("typeof"); + writeSpace(); emitExpression(node.expression); } function emitVoidExpression(node: VoidExpression) { - write("void "); + writeKeyword("void"); + writeSpace(); emitExpression(node.expression); } function emitAwaitExpression(node: AwaitExpression) { - write("await "); + writeKeyword("await"); + writeSpace(); emitExpression(node.expression); } function emitPrefixUnaryExpression(node: PrefixUnaryExpression) { - writeTokenText(node.operator); + writeTokenText(node.operator, writeOperator); if (shouldEmitWhitespaceBeforeOperand(node)) { - write(" "); + writeSpace(); } emitExpression(node.operand); } @@ -1393,7 +1454,7 @@ namespace ts { function emitPostfixUnaryExpression(node: PostfixUnaryExpression) { emitExpression(node.operand); - writeTokenText(node.operator); + writeTokenText(node.operator, writeOperator); } function emitBinaryExpression(node: BinaryExpression) { @@ -1404,7 +1465,7 @@ namespace ts { emitExpression(node.left); increaseIndentIf(indentBeforeOperator, isCommaOperator ? " " : undefined); emitLeadingCommentsOfPosition(node.operatorToken.pos); - writeTokenNode(node.operatorToken); + writeTokenNode(node.operatorToken, writeOperator); emitTrailingCommentsOfPosition(node.operatorToken.end, /*prefixSpace*/ true); // Binary operators should have a space before the comment starts increaseIndentIf(indentAfterOperator, " "); emitExpression(node.right); @@ -1437,13 +1498,13 @@ namespace ts { } function emitYieldExpression(node: YieldExpression) { - write("yield"); + writeKeyword("yield"); emit(node.asteriskToken); - emitExpressionWithPrefix(" ", node.expression); + emitExpressionWithLeadingSpace(node.expression); } function emitSpreadExpression(node: SpreadElement) { - write("..."); + writePunctuation("..."); emitExpression(node.expression); } @@ -1459,19 +1520,21 @@ namespace ts { function emitAsExpression(node: AsExpression) { emitExpression(node.expression); if (node.type) { - write(" as "); + writeSpace(); + writeKeyword("as"); + writeSpace(); emit(node.type); } } function emitNonNullExpression(node: NonNullExpression) { emitExpression(node.expression); - write("!"); + writeOperator("!"); } function emitMetaProperty(node: MetaProperty) { - writeToken(node.keywordToken, node.pos); - write("."); + writeToken(node.keywordToken, node.pos, writePunctuation); + writePunctuation("."); emit(node.name); } @@ -1489,13 +1552,13 @@ namespace ts { // function emitBlock(node: Block) { - writeToken(SyntaxKind.OpenBraceToken, node.pos, /*contextNode*/ node); + writeToken(SyntaxKind.OpenBraceToken, node.pos, writePunctuation, /*contextNode*/ node); emitBlockStatements(node, /*forceSingleLine*/ !node.multiLine && isEmptyBlock(node)); // We have to call emitLeadingComments explicitly here because otherwise leading comments of the close brace token will not be emitted increaseIndent(); emitLeadingCommentsOfPosition(node.statements.end); decreaseIndent(); - writeToken(SyntaxKind.CloseBraceToken, node.statements.end, /*contextNode*/ node); + writeToken(SyntaxKind.CloseBraceToken, node.statements.end, writePunctuation, /*contextNode*/ node); } function emitBlockStatements(node: BlockLike, forceSingleLine: boolean) { @@ -1506,30 +1569,30 @@ namespace ts { function emitVariableStatement(node: VariableStatement) { emitModifiers(node, node.modifiers); emit(node.declarationList); - write(";"); + writeSemicolon(); } function emitEmptyStatement() { - write(";"); + writeSemicolon(); } function emitExpressionStatement(node: ExpressionStatement) { emitExpression(node.expression); - write(";"); + writeSemicolon(); } function emitIfStatement(node: IfStatement) { - const openParenPos = writeToken(SyntaxKind.IfKeyword, node.pos, node); - write(" "); - writeToken(SyntaxKind.OpenParenToken, openParenPos, node); + const openParenPos = writeToken(SyntaxKind.IfKeyword, node.pos, writeKeyword, node); + writeSpace(); + writeToken(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, node); emitExpression(node.expression); - writeToken(SyntaxKind.CloseParenToken, node.expression.end, node); + writeToken(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation, node); emitEmbeddedStatement(node, node.thenStatement); if (node.elseStatement) { writeLineOrSpace(node); - writeToken(SyntaxKind.ElseKeyword, node.thenStatement.end, node); + writeToken(SyntaxKind.ElseKeyword, node.thenStatement.end, writeKeyword, node); if (node.elseStatement.kind === SyntaxKind.IfStatement) { - write(" "); + writeSpace(); emit(node.elseStatement); } else { @@ -1539,60 +1602,68 @@ namespace ts { } function emitDoStatement(node: DoStatement) { - write("do"); + writeKeyword("do"); emitEmbeddedStatement(node, node.statement); if (isBlock(node.statement)) { - write(" "); + writeSpace(); } else { writeLineOrSpace(node); } - write("while ("); + writeKeyword("while"); + writeSpace(); + writePunctuation("("); emitExpression(node.expression); - write(");"); + writePunctuation(");"); } function emitWhileStatement(node: WhileStatement) { - write("while ("); + writeKeyword("while"); + writeSpace(); + writePunctuation("("); emitExpression(node.expression); - write(")"); + writePunctuation(")"); emitEmbeddedStatement(node, node.statement); } function emitForStatement(node: ForStatement) { - const openParenPos = writeToken(SyntaxKind.ForKeyword, node.pos); - write(" "); - writeToken(SyntaxKind.OpenParenToken, openParenPos, /*contextNode*/ node); + const openParenPos = writeToken(SyntaxKind.ForKeyword, node.pos, writeKeyword); + writeSpace(); + writeToken(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, /*contextNode*/ node); emitForBinding(node.initializer); - write(";"); - emitExpressionWithPrefix(" ", node.condition); - write(";"); - emitExpressionWithPrefix(" ", node.incrementor); - write(")"); + writeSemicolon(); + emitExpressionWithLeadingSpace(node.condition); + writeSemicolon(); + emitExpressionWithLeadingSpace(node.incrementor); + writePunctuation(")"); emitEmbeddedStatement(node, node.statement); } function emitForInStatement(node: ForInStatement) { - const openParenPos = writeToken(SyntaxKind.ForKeyword, node.pos); - write(" "); - writeToken(SyntaxKind.OpenParenToken, openParenPos); + const openParenPos = writeToken(SyntaxKind.ForKeyword, node.pos, writeKeyword); + writeSpace(); + writeToken(SyntaxKind.OpenParenToken, openParenPos, writePunctuation); emitForBinding(node.initializer); - write(" in "); + writeSpace(); + writeKeyword("in"); + writeSpace(); emitExpression(node.expression); - writeToken(SyntaxKind.CloseParenToken, node.expression.end); + writeToken(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation); emitEmbeddedStatement(node, node.statement); } function emitForOfStatement(node: ForOfStatement) { - const openParenPos = writeToken(SyntaxKind.ForKeyword, node.pos); - write(" "); - emitWithSuffix(node.awaitModifier, " "); - writeToken(SyntaxKind.OpenParenToken, openParenPos); + const openParenPos = writeToken(SyntaxKind.ForKeyword, node.pos, writeKeyword); + writeSpace(); + emitWithTrailingSpace(node.awaitModifier); + writeToken(SyntaxKind.OpenParenToken, openParenPos, writePunctuation); emitForBinding(node.initializer); - write(" of "); + writeSpace(); + writeKeyword("of"); + writeSpace(); emitExpression(node.expression); - writeToken(SyntaxKind.CloseParenToken, node.expression.end); + writeToken(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation); emitEmbeddedStatement(node, node.statement); } @@ -1608,23 +1679,23 @@ namespace ts { } function emitContinueStatement(node: ContinueStatement) { - writeToken(SyntaxKind.ContinueKeyword, node.pos); - emitWithPrefix(" ", node.label); - write(";"); + writeToken(SyntaxKind.ContinueKeyword, node.pos, writeKeyword); + emitWithLeadingSpace(node.label); + writeSemicolon(); } function emitBreakStatement(node: BreakStatement) { - writeToken(SyntaxKind.BreakKeyword, node.pos); - emitWithPrefix(" ", node.label); - write(";"); + writeToken(SyntaxKind.BreakKeyword, node.pos, writeKeyword); + emitWithLeadingSpace(node.label); + writeSemicolon(); } - function emitTokenWithComment(token: SyntaxKind, pos: number, contextNode?: Node) { + function emitTokenWithComment(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode?: Node) { const node = contextNode && getParseTreeNode(contextNode); if (node && node.kind === contextNode.kind) { pos = skipTrivia(currentSourceFile.text, pos); } - pos = writeToken(token, pos, /*contextNode*/ contextNode); + pos = writeToken(token, pos, writer, /*contextNode*/ contextNode); if (node && node.kind === contextNode.kind) { emitTrailingCommentsOfPosition(pos, /*prefixSpace*/ true); } @@ -1632,42 +1703,46 @@ namespace ts { } function emitReturnStatement(node: ReturnStatement) { - emitTokenWithComment(SyntaxKind.ReturnKeyword, node.pos, /*contextNode*/ node); - emitExpressionWithPrefix(" ", node.expression); - write(";"); + emitTokenWithComment(SyntaxKind.ReturnKeyword, node.pos, writeKeyword, /*contextNode*/ node); + emitExpressionWithLeadingSpace(node.expression); + writeSemicolon(); } function emitWithStatement(node: WithStatement) { - write("with ("); + writeKeyword("with"); + writeSpace(); + writePunctuation("("); emitExpression(node.expression); - write(")"); + writePunctuation(")"); emitEmbeddedStatement(node, node.statement); } function emitSwitchStatement(node: SwitchStatement) { - const openParenPos = writeToken(SyntaxKind.SwitchKeyword, node.pos); - write(" "); - writeToken(SyntaxKind.OpenParenToken, openParenPos); + const openParenPos = writeToken(SyntaxKind.SwitchKeyword, node.pos, writeKeyword); + writeSpace(); + writeToken(SyntaxKind.OpenParenToken, openParenPos, writePunctuation); emitExpression(node.expression); - writeToken(SyntaxKind.CloseParenToken, node.expression.end); - write(" "); + writeToken(SyntaxKind.CloseParenToken, node.expression.end, writePunctuation); + writeSpace(); emit(node.caseBlock); } function emitLabeledStatement(node: LabeledStatement) { emit(node.label); - write(": "); + writePunctuation(":"); + writeSpace(); emit(node.statement); } function emitThrowStatement(node: ThrowStatement) { - write("throw"); - emitExpressionWithPrefix(" ", node.expression); - write(";"); + writeKeyword("throw"); + emitExpressionWithLeadingSpace(node.expression); + writeSemicolon(); } function emitTryStatement(node: TryStatement) { - write("try "); + writeKeyword("try"); + writeSpace(); emit(node.tryBlock); if (node.catchClause) { writeLineOrSpace(node); @@ -1675,14 +1750,15 @@ namespace ts { } if (node.finallyBlock) { writeLineOrSpace(node); - write("finally "); + writeKeyword("finally"); + writeSpace(); emit(node.finallyBlock); } } function emitDebuggerStatement(node: DebuggerStatement) { - writeToken(SyntaxKind.DebuggerKeyword, node.pos); - write(";"); + writeToken(SyntaxKind.DebuggerKeyword, node.pos, writeKeyword); + writeSemicolon(); } // @@ -1691,12 +1767,13 @@ namespace ts { function emitVariableDeclaration(node: VariableDeclaration) { emit(node.name); - emitWithPrefix(": ", node.type); - emitExpressionWithPrefix(" = ", node.initializer); + emitTypeAnnotation(node.type); + emitInitializer(node.initializer); } function emitVariableDeclarationList(node: VariableDeclarationList) { - write(isLet(node) ? "let " : isConst(node) ? "const " : "var "); + writeKeyword(isLet(node) ? "let" : isConst(node) ? "const" : "var"); + writeSpace(); emitList(node, node.declarations, ListFormat.VariableDeclarationList); } @@ -1707,9 +1784,9 @@ namespace ts { function emitFunctionDeclarationOrExpression(node: FunctionDeclaration | FunctionExpression) { emitDecorators(node, node.decorators); emitModifiers(node, node.modifiers); - write("function"); + writeKeyword("function"); emitIfPresent(node.asteriskToken); - write(" "); + writeSpace(); emitIdentifierName(node.name); emitSignatureAndBody(node, emitSignatureHead); } @@ -1743,13 +1820,13 @@ namespace ts { } else { emitSignatureHead(node); - write(" "); + writeSpace(); emitExpression(body); } } else { emitSignatureHead(node); - write(";"); + writeSemicolon(); } } @@ -1757,7 +1834,7 @@ namespace ts { function emitSignatureHead(node: FunctionDeclaration | FunctionExpression | MethodDeclaration | AccessorDeclaration | ConstructorDeclaration) { emitTypeParameters(node, node.typeParameters); emitParameters(node, node.parameters); - emitWithPrefix(": ", node.type); + emitTypeAnnotation(node.type); } function shouldEmitBlockFunctionBodyOnSingleLine(body: Block) { @@ -1799,7 +1876,8 @@ namespace ts { } function emitBlockFunctionBody(body: Block) { - write(" {"); + writeSpace(); + writePunctuation("{"); increaseIndent(); const emitBlockFunctionBody = shouldEmitBlockFunctionBodyOnSingleLine(body) @@ -1814,7 +1892,7 @@ namespace ts { } decreaseIndent(); - writeToken(SyntaxKind.CloseBraceToken, body.statements.end, body); + writeToken(SyntaxKind.CloseBraceToken, body.statements.end, writePunctuation, body); } function emitBlockFunctionBodyOnSingleLine(body: Block) { @@ -1843,8 +1921,11 @@ namespace ts { function emitClassDeclarationOrExpression(node: ClassDeclaration | ClassExpression) { emitDecorators(node, node.decorators); emitModifiers(node, node.modifiers); - write("class"); - emitNodeWithPrefix(" ", node.name, emitIdentifierName); + writeKeyword("class"); + if (node.name) { + writeSpace(); + emitIdentifierName(node.name); + } const indentedFlag = getEmitFlags(node) & EmitFlags.Indented; if (indentedFlag) { @@ -1854,9 +1935,10 @@ namespace ts { emitTypeParameters(node, node.typeParameters); emitList(node, node.heritageClauses, ListFormat.ClassHeritageClauses); - write(" {"); + writeSpace(); + writePunctuation("{"); emitList(node, node.members, ListFormat.ClassMembers); - write("}"); + writePunctuation("}"); if (indentedFlag) { decreaseIndent(); @@ -1866,74 +1948,86 @@ namespace ts { function emitInterfaceDeclaration(node: InterfaceDeclaration) { emitDecorators(node, node.decorators); emitModifiers(node, node.modifiers); - write("interface "); + writeKeyword("interface"); + writeSpace(); emit(node.name); emitTypeParameters(node, node.typeParameters); emitList(node, node.heritageClauses, ListFormat.HeritageClauses); - write(" {"); + writeSpace(); + writePunctuation("{"); emitList(node, node.members, ListFormat.InterfaceMembers); - write("}"); + writePunctuation("}"); } function emitTypeAliasDeclaration(node: TypeAliasDeclaration) { emitDecorators(node, node.decorators); emitModifiers(node, node.modifiers); - write("type "); + writeKeyword("type"); + writeSpace(); emit(node.name); emitTypeParameters(node, node.typeParameters); - write(" = "); + writeSpace(); + writePunctuation("="); + writeSpace(); emit(node.type); - write(";"); + writeSemicolon(); } function emitEnumDeclaration(node: EnumDeclaration) { emitModifiers(node, node.modifiers); - write("enum "); + writeKeyword("enum"); + writeSpace(); emit(node.name); - write(" {"); + + writeSpace(); + writePunctuation("{"); emitList(node, node.members, ListFormat.EnumMembers); - write("}"); + writePunctuation("}"); } function emitModuleDeclaration(node: ModuleDeclaration) { emitModifiers(node, node.modifiers); if (~node.flags & NodeFlags.GlobalAugmentation) { - write(node.flags & NodeFlags.Namespace ? "namespace " : "module "); + writeKeyword(node.flags & NodeFlags.Namespace ? "namespace" : "module"); + writeSpace(); } emit(node.name); let body = node.body; while (body.kind === SyntaxKind.ModuleDeclaration) { - write("."); + writePunctuation("."); emit((body).name); body = (body).body; } - write(" "); + writeSpace(); emit(body); } function emitModuleBlock(node: ModuleBlock) { pushNameGenerationScope(node); - write("{"); + writePunctuation("{"); emitBlockStatements(node, /*forceSingleLine*/ isEmptyBlock(node)); - write("}"); + writePunctuation("}"); popNameGenerationScope(node); } function emitCaseBlock(node: CaseBlock) { - writeToken(SyntaxKind.OpenBraceToken, node.pos); + writeToken(SyntaxKind.OpenBraceToken, node.pos, writePunctuation); emitList(node, node.clauses, ListFormat.CaseBlockClauses); - writeToken(SyntaxKind.CloseBraceToken, node.clauses.end); + writeToken(SyntaxKind.CloseBraceToken, node.clauses.end, writePunctuation); } function emitImportEqualsDeclaration(node: ImportEqualsDeclaration) { emitModifiers(node, node.modifiers); - write("import "); + writeKeyword("import"); + writeSpace(); emit(node.name); - write(" = "); + writeSpace(); + writePunctuation("="); + writeSpace(); emitModuleReference(node.moduleReference); - write(";"); + writeSemicolon(); } function emitModuleReference(node: ModuleReference) { @@ -1947,25 +2041,32 @@ namespace ts { function emitImportDeclaration(node: ImportDeclaration) { emitModifiers(node, node.modifiers); - write("import "); + writeKeyword("import"); + writeSpace(); if (node.importClause) { emit(node.importClause); - write(" from "); + writeSpace(); + writeKeyword("from"); + writeSpace(); } emitExpression(node.moduleSpecifier); - write(";"); + writeSemicolon(); } function emitImportClause(node: ImportClause) { emit(node.name); if (node.name && node.namedBindings) { - write(", "); + writePunctuation(","); + writeSpace(); } emit(node.namedBindings); } function emitNamespaceImport(node: NamespaceImport) { - write("* as "); + writePunctuation("*"); + writeSpace(); + writeKeyword("as"); + writeSpace(); emit(node.name); } @@ -1978,30 +2079,46 @@ namespace ts { } function emitExportAssignment(node: ExportAssignment) { - write(node.isExportEquals ? "export = " : "export default "); + writeKeyword("export"); + writeSpace(); + if (node.isExportEquals) { + writeOperator("="); + } + else { + writeKeyword("default"); + } + writeSpace(); emitExpression(node.expression); - write(";"); + writeSemicolon(); } function emitExportDeclaration(node: ExportDeclaration) { - write("export "); + writeKeyword("export"); + writeSpace(); if (node.exportClause) { emit(node.exportClause); } else { - write("*"); + writePunctuation("*"); } if (node.moduleSpecifier) { - write(" from "); + writeSpace(); + writeKeyword("from"); + writeSpace(); emitExpression(node.moduleSpecifier); } - write(";"); + writeSemicolon(); } function emitNamespaceExportDeclaration(node: NamespaceExportDeclaration) { - write("export as namespace "); + writeKeyword("export"); + writeSpace(); + writeKeyword("as"); + writeSpace(); + writeKeyword("namespace"); + writeSpace(); emit(node.name); - write(";"); + writeSemicolon(); } function emitNamedExports(node: NamedExports) { @@ -2013,15 +2130,17 @@ namespace ts { } function emitNamedImportsOrExports(node: NamedImportsOrExports) { - write("{"); + writePunctuation("{"); emitList(node, node.elements, ListFormat.NamedImportsOrExportsElements); - write("}"); + writePunctuation("}"); } function emitImportOrExportSpecifier(node: ImportOrExportSpecifier) { if (node.propertyName) { emit(node.propertyName); - write(" as "); + writeSpace(); + writeKeyword("as"); + writeSpace(); } emit(node.name); @@ -2032,9 +2151,10 @@ namespace ts { // function emitExternalModuleReference(node: ExternalModuleReference) { - write("require("); + writeKeyword("require"); + writePunctuation("("); emitExpression(node.expression); - write(")"); + writePunctuation(")"); } // @@ -2048,14 +2168,14 @@ namespace ts { } function emitJsxSelfClosingElement(node: JsxSelfClosingElement) { - write("<"); + writePunctuation("<"); emitJsxTagName(node.tagName); - write(" "); + writeSpace(); // We are checking here so we won't re-enter the emiting pipeline and emit extra sourcemap if (node.attributes.properties && node.attributes.properties.length > 0) { emit(node.attributes); } - write("/>"); + writePunctuation("/>"); } function emitJsxFragment(node: JsxFragment) { @@ -2065,30 +2185,31 @@ namespace ts { } function emitJsxOpeningElementOrFragment(node: JsxOpeningElement | JsxOpeningFragment) { - write("<"); + writePunctuation("<"); if (isJsxOpeningElement(node)) { emitJsxTagName(node.tagName); // We are checking here so we won't re-enter the emitting pipeline and emit extra sourcemap if (node.attributes.properties && node.attributes.properties.length > 0) { - write(" "); + writeSpace(); emit(node.attributes); } } - write(">"); + writePunctuation(">"); } function emitJsxText(node: JsxText) { + commitPendingSemicolon(); writer.writeLiteral(getTextOfNode(node, /*includeTrivia*/ true)); } function emitJsxClosingElementOrFragment(node: JsxClosingElement | JsxClosingFragment) { - write(""); + writePunctuation(">"); } function emitJsxAttributes(node: JsxAttributes) { @@ -2097,21 +2218,21 @@ namespace ts { function emitJsxAttribute(node: JsxAttribute) { emit(node.name); - emitWithPrefix("=", node.initializer); + emitNodeWithPrefix("=", writePunctuation, node.initializer, emit); } function emitJsxSpreadAttribute(node: JsxSpreadAttribute) { - write("{..."); + writePunctuation("{..."); emitExpression(node.expression); - write("}"); + writePunctuation("}"); } function emitJsxExpression(node: JsxExpression) { if (node.expression) { - write("{"); + writePunctuation("{"); emitIfPresent(node.dotDotDotToken); emitExpression(node.expression); - write("}"); + writePunctuation("}"); } } @@ -2129,15 +2250,17 @@ namespace ts { // function emitCaseClause(node: CaseClause) { - write("case "); + writeKeyword("case"); + writeSpace(); emitExpression(node.expression); - write(":"); + writePunctuation(":"); emitCaseOrDefaultClauseStatements(node, node.statements); } function emitDefaultClause(node: DefaultClause) { - write("default:"); + writeKeyword("default"); + writePunctuation(":"); emitCaseOrDefaultClauseStatements(node, node.statements); } @@ -2169,27 +2292,27 @@ namespace ts { let format = ListFormat.CaseOrDefaultClauseStatements; if (emitAsSingleStatement) { - write(" "); + writeSpace(); format &= ~(ListFormat.MultiLine | ListFormat.Indented); } emitList(parentNode, statements, format); } function emitHeritageClause(node: HeritageClause) { - write(" "); - writeTokenText(node.token); - write(" "); + writeSpace(); + writeTokenText(node.token, writeKeyword); + writeSpace(); emitList(node, node.types, ListFormat.HeritageClauseTypes); } function emitCatchClause(node: CatchClause) { - const openParenPos = writeToken(SyntaxKind.CatchKeyword, node.pos); - write(" "); + const openParenPos = writeToken(SyntaxKind.CatchKeyword, node.pos, writeKeyword); + writeSpace(); if (node.variableDeclaration) { - writeToken(SyntaxKind.OpenParenToken, openParenPos); + writeToken(SyntaxKind.OpenParenToken, openParenPos, writePunctuation); emit(node.variableDeclaration); - writeToken(SyntaxKind.CloseParenToken, node.variableDeclaration.end); - write(" "); + writeToken(SyntaxKind.CloseParenToken, node.variableDeclaration.end, writePunctuation); + writeSpace(); } emit(node.block); } @@ -2200,7 +2323,8 @@ namespace ts { function emitPropertyAssignment(node: PropertyAssignment) { emit(node.name); - write(": "); + writePunctuation(":"); + writeSpace(); // This is to ensure that we emit comment in the following case: // For example: // obj = { @@ -2219,14 +2343,16 @@ namespace ts { function emitShorthandPropertyAssignment(node: ShorthandPropertyAssignment) { emit(node.name); if (node.objectAssignmentInitializer) { - write(" = "); + writeSpace(); + writePunctuation("="); + writeSpace(); emitExpression(node.objectAssignmentInitializer); } } function emitSpreadAssignment(node: SpreadAssignment) { if (node.expression) { - write("..."); + writePunctuation("..."); emitExpression(node.expression); } } @@ -2237,7 +2363,7 @@ namespace ts { function emitEnumMember(node: EnumMember) { emit(node.name); - emitExpressionWithPrefix(" = ", node.initializer); + emitInitializer(node.initializer); } // @@ -2345,38 +2471,68 @@ namespace ts { // Helpers // + function emitNodeWithWriter(node: Node, writer: typeof write) { + const savedWrite = write; + write = writer; + emit(node); + write = savedWrite; + } + function emitModifiers(node: Node, modifiers: NodeArray) { if (modifiers && modifiers.length) { emitList(node, modifiers, ListFormat.Modifiers); - write(" "); + writeSpace(); } } - function emitWithPrefix(prefix: string, node: Node) { - emitNodeWithPrefix(prefix, node, emit); - } - - function emitExpressionWithPrefix(prefix: string, node: Node) { - emitNodeWithPrefix(prefix, node, emitExpression); - } - - function emitNodeWithPrefix(prefix: string, node: Node, emit: (node: Node) => void) { + function emitTypeAnnotation(node: TypeNode | undefined) { if (node) { - write(prefix); + writePunctuation(":"); + writeSpace(); emit(node); } } - function emitWithSuffix(node: Node, suffix: string) { + function emitInitializer(node: Expression | undefined) { + if (node) { + writeSpace(); + writeOperator("="); + writeSpace(); + emitExpression(node); + } + } + + function emitNodeWithPrefix(prefix: string, prefixWriter: (s: string) => void, node: Node, emit: (node: Node) => void) { + if (node) { + prefixWriter(prefix); + emit(node); + } + } + + function emitWithLeadingSpace(node: Node | undefined) { + if (node) { + writeSpace(); + emit(node); + } + } + + function emitExpressionWithLeadingSpace(node: Expression | undefined) { + if (node) { + writeSpace(); + emitExpression(node); + } + } + + function emitWithTrailingSpace(node: Node | undefined) { if (node) { emit(node); - write(suffix); + writeSpace(); } } function emitEmbeddedStatement(parent: Node, node: Statement) { if (isBlock(node) || getEmitFlags(parent) & EmitFlags.SingleLine) { - write(" "); + writeSpace(); emit(node); } else { @@ -2395,7 +2551,10 @@ namespace ts { emitList(parentNode, typeArguments, ListFormat.TypeArguments); } - function emitTypeParameters(parentNode: Node, typeParameters: NodeArray) { + function emitTypeParameters(parentNode: SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration | ClassDeclaration | ClassExpression, typeParameters: NodeArray) { + if (isFunctionLike(parentNode) && parentNode.typeArguments) { // Quick info uses type arguments in place of type parameters on instantiated signatures + return emitTypeArguments(parentNode, parentNode.typeArguments); + } emitList(parentNode, typeParameters, ListFormat.TypeParameters); } @@ -2433,15 +2592,33 @@ namespace ts { emitList(parentNode, parameters, ListFormat.IndexSignatureParameters); } - function emitList(parentNode: Node, children: NodeArray, format: ListFormat, start?: number, count?: number) { + function emitList(parentNode: TextRange, children: NodeArray, format: ListFormat, start?: number, count?: number) { emitNodeList(emit, parentNode, children, format, start, count); } - function emitExpressionList(parentNode: Node, children: NodeArray, format: ListFormat, start?: number, count?: number) { + function emitExpressionList(parentNode: TextRange, children: NodeArray, format: ListFormat, start?: number, count?: number) { emitNodeList(emitExpression, parentNode, children, format, start, count); } - function emitNodeList(emit: (node: Node) => void, parentNode: Node, children: NodeArray, format: ListFormat, start = 0, count = children ? children.length - start : 0) { + function writeDelimiter(format: ListFormat) { + switch (format & ListFormat.DelimitersMask) { + case ListFormat.None: + break; + case ListFormat.CommaDelimited: + writePunctuation(","); + break; + case ListFormat.BarDelimited: + writeSpace(); + writePunctuation("|"); + break; + case ListFormat.AmpersandDelimited: + writeSpace(); + writePunctuation("&"); + break; + } + } + + function emitNodeList(emit: (node: Node) => void, parentNode: TextRange, children: NodeArray, format: ListFormat, start = 0, count = children ? children.length - start : 0) { const isUndefined = children === undefined; if (isUndefined && format & ListFormat.OptionalIfUndefined) { return; @@ -2459,7 +2636,7 @@ namespace ts { } if (format & ListFormat.BracketsMask) { - write(getOpeningBracket(format)); + writePunctuation(getOpeningBracket(format)); } if (onBeforeEmitNodeArray) { @@ -2472,7 +2649,7 @@ namespace ts { writeLine(); } else if (format & ListFormat.SpaceBetweenBraces && !(format & ListFormat.NoSpaceIfEmpty)) { - write(" "); + writeSpace(); } } else { @@ -2484,7 +2661,7 @@ namespace ts { shouldEmitInterveningComments = false; } else if (format & ListFormat.SpaceBetweenBraces) { - write(" "); + writeSpace(); } // Increase the indent, if requested. @@ -2495,7 +2672,6 @@ namespace ts { // Emit each child. let previousSibling: Node; let shouldDecreaseIndentAfterEmit: boolean; - const delimiter = getDelimiter(format); for (let i = 0; i < count; i++) { const child = children[start + i]; @@ -2507,10 +2683,10 @@ namespace ts { // a // /* End of parameter a */ -> this comment isn't considered to be trailing comment of parameter "a" due to newline // , - if (delimiter && previousSibling.end !== parentNode.end) { + if (format & ListFormat.DelimitersMask && previousSibling.end !== parentNode.end) { emitLeadingCommentsOfPosition(previousSibling.end); } - write(delimiter); + writeDelimiter(format); // Write either a line terminator or whitespace to separate the elements. if (shouldWriteSeparatingLineTerminator(previousSibling, child, format)) { @@ -2525,7 +2701,7 @@ namespace ts { shouldEmitInterveningComments = false; } else if (previousSibling && format & ListFormat.SpaceBetweenSiblings) { - write(" "); + writeSpace(); } } @@ -2553,7 +2729,7 @@ namespace ts { // Write a trailing comma, if requested. const hasTrailingComma = (format & ListFormat.AllowTrailingComma) && children.hasTrailingComma; if (format & ListFormat.CommaDelimited && hasTrailingComma) { - write(","); + writePunctuation(","); } @@ -2563,7 +2739,7 @@ namespace ts { // 2 // /* end of element 2 */ // ]; - if (previousSibling && delimiter && previousSibling.end !== parentNode.end && !(getEmitFlags(previousSibling) & EmitFlags.NoTrailingComments)) { + if (previousSibling && format & ListFormat.DelimitersMask && previousSibling.end !== parentNode.end && !(getEmitFlags(previousSibling) & EmitFlags.NoTrailingComments)) { emitLeadingCommentsOfPosition(previousSibling.end); } @@ -2577,7 +2753,7 @@ namespace ts { writeLine(); } else if (format & ListFormat.SpaceBetweenBraces) { - write(" "); + writeSpace(); } } @@ -2586,51 +2762,115 @@ namespace ts { } if (format & ListFormat.BracketsMask) { - write(getClosingBracket(format)); + writePunctuation(getClosingBracket(format)); } } - function write(s: string) { + function commitPendingSemicolonInternal() { + if (pendingSemicolon) { + writeSemicolonInternal(); + pendingSemicolon = false; + } + } + + function writeLiteral(s: string) { + commitPendingSemicolon(); + writer.writeLiteral(s); + } + + function writeStringLiteral(s: string) { + commitPendingSemicolon(); + writer.writeStringLiteral(s); + } + + function writeBase(s: string) { + commitPendingSemicolon(); writer.write(s); } + function writeSymbol(s: string, sym: Symbol) { + commitPendingSemicolon(); + writer.writeSymbol(s, sym); + } + + function writePunctuation(s: string) { + commitPendingSemicolon(); + writer.writePunctuation(s); + } + + function deferWriteSemicolon() { + pendingSemicolon = true; + } + + function writeSemicolonInternal() { + writer.writePunctuation(";"); + } + + function writeKeyword(s: string) { + commitPendingSemicolon(); + writer.writeKeyword(s); + } + + function writeOperator(s: string) { + commitPendingSemicolon(); + writer.writeOperator(s); + } + + function writeParameter(s: string) { + commitPendingSemicolon(); + writer.writeParameter(s); + } + + function writeSpace() { + commitPendingSemicolon(); + writer.writeSpace(" "); + } + + function writeProperty(s: string) { + commitPendingSemicolon(); + writer.writeProperty(s); + } + function writeLine() { + commitPendingSemicolon(); writer.writeLine(); } function increaseIndent() { + commitPendingSemicolon(); writer.increaseIndent(); } function decreaseIndent() { + commitPendingSemicolon(); writer.decreaseIndent(); } - function writeToken(token: SyntaxKind, pos: number, contextNode?: Node) { + function writeToken(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode?: Node) { return onEmitSourceMapOfToken - ? onEmitSourceMapOfToken(contextNode, token, pos, writeTokenText) - : writeTokenText(token, pos); + ? onEmitSourceMapOfToken(contextNode, token, writer, pos, writeTokenText) + : writeTokenText(token, writer, pos); } - function writeTokenNode(node: Node) { + function writeTokenNode(node: Node, writer: (s: string) => void) { if (onBeforeEmitToken) { onBeforeEmitToken(node); } - write(tokenToString(node.kind)); + writer(tokenToString(node.kind)); if (onAfterEmitToken) { onAfterEmitToken(node); } } - function writeTokenText(token: SyntaxKind, pos?: number) { + function writeTokenText(token: SyntaxKind, writer: (s: string) => void, pos?: number) { const tokenString = tokenToString(token); - write(tokenString); + writer(tokenString); return pos < 0 ? pos : pos + tokenString.length; } function writeLineOrSpace(node: Node) { if (getEmitFlags(node) & EmitFlags.SingleLine) { - write(" "); + writeSpace(); } else { writeLine(); @@ -2688,7 +2928,7 @@ namespace ts { } } - function shouldWriteLeadingLineTerminator(parentNode: Node, children: NodeArray, format: ListFormat) { + function shouldWriteLeadingLineTerminator(parentNode: TextRange, children: NodeArray, format: ListFormat) { if (format & ListFormat.MultiLine) { return true; } @@ -2734,7 +2974,7 @@ namespace ts { } } - function shouldWriteClosingLineTerminator(parentNode: Node, children: NodeArray, format: ListFormat) { + function shouldWriteClosingLineTerminator(parentNode: TextRange, children: NodeArray, format: ListFormat) { if (format & ListFormat.MultiLine) { return (format & ListFormat.NoTrailingNewLine) === 0; } @@ -3073,19 +3313,6 @@ namespace ts { } } - function createDelimiterMap() { - const delimiters: string[] = []; - delimiters[ListFormat.None] = ""; - delimiters[ListFormat.CommaDelimited] = ","; - delimiters[ListFormat.BarDelimited] = " |"; - delimiters[ListFormat.AmpersandDelimited] = " &"; - return delimiters; - } - - function getDelimiter(format: ListFormat) { - return delimiters[format & ListFormat.DelimitersMask]; - } - function createBracketsMap() { const brackets: string[][] = []; brackets[ListFormat.Braces] = ["{", "}"]; @@ -3109,86 +3336,4 @@ namespace ts { CountMask = 0x0FFFFFFF, // Temp variable counter _i = 0x10000000, // Use/preference flag for '_i' } - - const enum ListFormat { - None = 0, - - // Line separators - SingleLine = 0, // Prints the list on a single line (default). - MultiLine = 1 << 0, // Prints the list on multiple lines. - PreserveLines = 1 << 1, // Prints the list using line preservation if possible. - LinesMask = SingleLine | MultiLine | PreserveLines, - - // Delimiters - NotDelimited = 0, // There is no delimiter between list items (default). - BarDelimited = 1 << 2, // Each list item is space-and-bar (" |") delimited. - AmpersandDelimited = 1 << 3, // Each list item is space-and-ampersand (" &") delimited. - CommaDelimited = 1 << 4, // Each list item is comma (",") delimited. - DelimitersMask = BarDelimited | AmpersandDelimited | CommaDelimited, - - AllowTrailingComma = 1 << 5, // Write a trailing comma (",") if present. - - // Whitespace - Indented = 1 << 6, // The list should be indented. - SpaceBetweenBraces = 1 << 7, // Inserts a space after the opening brace and before the closing brace. - SpaceBetweenSiblings = 1 << 8, // Inserts a space between each sibling node. - - // Brackets/Braces - Braces = 1 << 9, // The list is surrounded by "{" and "}". - Parenthesis = 1 << 10, // The list is surrounded by "(" and ")". - AngleBrackets = 1 << 11, // The list is surrounded by "<" and ">". - SquareBrackets = 1 << 12, // The list is surrounded by "[" and "]". - BracketsMask = Braces | Parenthesis | AngleBrackets | SquareBrackets, - - OptionalIfUndefined = 1 << 13, // Do not emit brackets if the list is undefined. - OptionalIfEmpty = 1 << 14, // Do not emit brackets if the list is empty. - Optional = OptionalIfUndefined | OptionalIfEmpty, - - // Other - PreferNewLine = 1 << 15, // Prefer adding a LineTerminator between synthesized nodes. - NoTrailingNewLine = 1 << 16, // Do not emit a trailing NewLine for a MultiLine list. - NoInterveningComments = 1 << 17, // Do not emit comments between each node - - NoSpaceIfEmpty = 1 << 18, // If the literal is empty, do not add spaces between braces. - SingleElement = 1 << 19, - - // Precomputed Formats - Modifiers = SingleLine | SpaceBetweenSiblings | NoInterveningComments, - HeritageClauses = SingleLine | SpaceBetweenSiblings, - SingleLineTypeLiteralMembers = SingleLine | SpaceBetweenBraces | SpaceBetweenSiblings | Indented, - MultiLineTypeLiteralMembers = MultiLine | Indented, - - TupleTypeElements = CommaDelimited | SpaceBetweenSiblings | SingleLine | Indented, - UnionTypeConstituents = BarDelimited | SpaceBetweenSiblings | SingleLine, - IntersectionTypeConstituents = AmpersandDelimited | SpaceBetweenSiblings | SingleLine, - ObjectBindingPatternElements = SingleLine | AllowTrailingComma | SpaceBetweenBraces | CommaDelimited | SpaceBetweenSiblings | NoSpaceIfEmpty, - ArrayBindingPatternElements = SingleLine | AllowTrailingComma | CommaDelimited | SpaceBetweenSiblings | NoSpaceIfEmpty, - ObjectLiteralExpressionProperties = PreserveLines | CommaDelimited | SpaceBetweenSiblings | SpaceBetweenBraces | Indented | Braces | NoSpaceIfEmpty, - ArrayLiteralExpressionElements = PreserveLines | CommaDelimited | SpaceBetweenSiblings | AllowTrailingComma | Indented | SquareBrackets, - CommaListElements = CommaDelimited | SpaceBetweenSiblings | SingleLine, - CallExpressionArguments = CommaDelimited | SpaceBetweenSiblings | SingleLine | Parenthesis, - NewExpressionArguments = CommaDelimited | SpaceBetweenSiblings | SingleLine | Parenthesis | OptionalIfUndefined, - TemplateExpressionSpans = SingleLine | NoInterveningComments, - SingleLineBlockStatements = SpaceBetweenBraces | SpaceBetweenSiblings | SingleLine, - MultiLineBlockStatements = Indented | MultiLine, - VariableDeclarationList = CommaDelimited | SpaceBetweenSiblings | SingleLine, - SingleLineFunctionBodyStatements = SingleLine | SpaceBetweenSiblings | SpaceBetweenBraces, - MultiLineFunctionBodyStatements = MultiLine, - ClassHeritageClauses = SingleLine | SpaceBetweenSiblings, - ClassMembers = Indented | MultiLine, - InterfaceMembers = Indented | MultiLine, - EnumMembers = CommaDelimited | Indented | MultiLine, - CaseBlockClauses = Indented | MultiLine, - NamedImportsOrExportsElements = CommaDelimited | SpaceBetweenSiblings | AllowTrailingComma | SingleLine | SpaceBetweenBraces, - JsxElementOrFragmentChildren = SingleLine | NoInterveningComments, - JsxElementAttributes = SingleLine | SpaceBetweenSiblings | NoInterveningComments, - CaseOrDefaultClauseStatements = Indented | MultiLine | NoTrailingNewLine | OptionalIfEmpty, - HeritageClauseTypes = CommaDelimited | SpaceBetweenSiblings | SingleLine, - SourceFileStatements = MultiLine | NoTrailingNewLine, - Decorators = MultiLine | Optional, - TypeArguments = CommaDelimited | SpaceBetweenSiblings | SingleLine | Indented | AngleBrackets | Optional, - TypeParameters = CommaDelimited | SpaceBetweenSiblings | SingleLine | Indented | AngleBrackets | Optional, - Parameters = CommaDelimited | SpaceBetweenSiblings | SingleLine | Indented | Parenthesis, - IndexSignatureParameters = CommaDelimited | SpaceBetweenSiblings | SingleLine | Indented | SquareBrackets, - } } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 7ddc8a0cd5f..14c8ad91eeb 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -112,21 +112,23 @@ namespace ts { export function createIdentifier(text: string): Identifier; /* @internal */ - // tslint:disable-next-line unified-signatures - export function createIdentifier(text: string, typeArguments: ReadonlyArray): Identifier; - export function createIdentifier(text: string, typeArguments?: ReadonlyArray): Identifier { + export function createIdentifier(text: string, typeArguments: ReadonlyArray): Identifier; // tslint:disable-line unified-signatures + export function createIdentifier(text: string, typeArguments?: ReadonlyArray): Identifier { const node = createSynthesizedNode(SyntaxKind.Identifier); node.escapedText = escapeLeadingUnderscores(text); node.originalKeywordKind = text ? stringToToken(text) : SyntaxKind.Unknown; node.autoGenerateKind = GeneratedIdentifierKind.None; node.autoGenerateId = 0; if (typeArguments) { - node.typeArguments = createNodeArray(typeArguments); + node.typeArguments = createNodeArray(typeArguments as ReadonlyArray); } return node; } - export function updateIdentifier(node: Identifier, typeArguments: NodeArray | undefined): Identifier { + export function updateIdentifier(node: Identifier): Identifier; + /* @internal */ + export function updateIdentifier(node: Identifier, typeArguments: NodeArray | undefined): Identifier; // tslint:disable-line unified-signatures + export function updateIdentifier(node: Identifier, typeArguments?: NodeArray | undefined): Identifier { return node.typeArguments !== typeArguments ? updateNode(createIdentifier(idText(node), typeArguments), node) : node; @@ -578,11 +580,12 @@ namespace ts { } /* @internal */ - export function createSignatureDeclaration(kind: SyntaxKind, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined) { + export function createSignatureDeclaration(kind: SyntaxKind, typeParameters: ReadonlyArray | undefined, parameters: ReadonlyArray, type: TypeNode | undefined, typeArguments?: TypeNode[] | undefined) { const node = createSynthesizedNode(kind) as SignatureDeclaration; node.typeParameters = asNodeArray(typeParameters); node.parameters = asNodeArray(parameters); node.type = type; + node.typeArguments = asNodeArray(typeArguments); return node; } diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 828c5744bbd..2dabb97b08d 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -51,7 +51,7 @@ namespace ts { * @param tokenStartPos The start pos of the token. * @param emitCallback The callback used to emit the token. */ - emitTokenWithSourceMap(node: Node, token: SyntaxKind, tokenStartPos: number, emitCallback: (token: SyntaxKind, tokenStartPos: number) => number): number; + emitTokenWithSourceMap(node: Node, token: SyntaxKind, writer: (s: string) => void, tokenStartPos: number, emitCallback: (token: SyntaxKind, writer: (s: string) => void, tokenStartPos: number) => number): number; /** * Gets the text for the source map. @@ -372,9 +372,9 @@ namespace ts { * @param tokenStartPos The start pos of the token. * @param emitCallback The callback used to emit the token. */ - function emitTokenWithSourceMap(node: Node, token: SyntaxKind, tokenPos: number, emitCallback: (token: SyntaxKind, tokenStartPos: number) => number) { + function emitTokenWithSourceMap(node: Node, token: SyntaxKind, writer: (s: string) => void, tokenPos: number, emitCallback: (token: SyntaxKind, writer: (s: string) => void, tokenStartPos: number) => number) { if (disabled) { - return emitCallback(token, tokenPos); + return emitCallback(token, writer, tokenPos); } const emitNode = node && node.emitNode; @@ -386,7 +386,7 @@ namespace ts { emitPos(tokenPos); } - tokenPos = emitCallback(token, tokenPos); + tokenPos = emitCallback(token, writer, tokenPos); if (range) tokenPos = range.end; if ((emitFlags & EmitFlags.NoTokenTrailingSourceMaps) === 0 && tokenPos >= 0) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c51208c4f79..6622348e101 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -695,7 +695,7 @@ namespace ts { /*@internal*/ autoGenerateKind?: GeneratedIdentifierKind; // Specifies whether to auto-generate the text for an identifier. /*@internal*/ autoGenerateId?: number; // Ensures unique generated identifiers get unique names, but clones get the same name. isInJSDocNamespace?: boolean; // if the node is a member in a JSDoc namespace - /*@internal*/ typeArguments?: NodeArray; // Only defined on synthesized nodes. Though not syntactically valid, used in emitting diagnostics. + /*@internal*/ typeArguments?: NodeArray; // Only defined on synthesized nodes. Though not syntactically valid, used in emitting diagnostics, quickinfo, and signature help. /*@internal*/ jsdocDotPos?: number; // Identifier occurs in JSDoc-style generic: Id. /*@internal*/ skipNameGenerationScope?: boolean; // Should skip a name generation scope when generating the name for this identifier } @@ -783,6 +783,7 @@ namespace ts { typeParameters?: NodeArray; parameters: NodeArray; type: TypeNode | undefined; + /* @internal */ typeArguments?: NodeArray; // Used for quick info, replaces typeParameters for instantiated signatures } export type SignatureDeclaration = @@ -966,7 +967,8 @@ namespace ts { | IndexSignatureDeclaration | MethodSignature | ConstructSignatureDeclaration - | CallSignatureDeclaration; + | CallSignatureDeclaration + | JSDocFunctionType; export interface FunctionDeclaration extends FunctionLikeDeclarationBase, DeclarationStatement { kind: SyntaxKind.FunctionDeclaration; @@ -2762,6 +2764,16 @@ namespace ts { signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): SignatureDeclaration; /** Note that the resulting nodes cannot be checked. */ indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): IndexSignatureDeclaration; + /** Note that the resulting nodes cannot be checked. */ + symbolToEntityName(symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): EntityName; + /** Note that the resulting nodes cannot be checked. */ + symbolToExpression(symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): Expression; + /** Note that the resulting nodes cannot be checked. */ + symbolToTypeParameterDeclarations(symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): NodeArray | undefined; + /** Note that the resulting nodes cannot be checked. */ + symbolToParameterDeclaration(symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): ParameterDeclaration; + /** Note that the resulting nodes cannot be checked. */ + typeParameterToDeclaration(parameter: TypeParameter, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): TypeParameterDeclaration; getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; getSymbolAtLocation(node: Node): Symbol | undefined; @@ -2780,9 +2792,17 @@ namespace ts { getPropertySymbolOfDestructuringAssignment(location: Identifier): Symbol | undefined; getTypeAtLocation(node: Node): Type; getTypeFromTypeNode(node: TypeNode): Type; + signatureToString(signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): string; typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string; - symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): string; + symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): string; + typePredicateToString(predicate: TypePredicate, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string; + + /* @internal */ writeSignature(signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind, writer?: EmitTextWriter): string; + /* @internal */ writeType(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags, writer?: EmitTextWriter): string; + /* @internal */ writeSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags, writer?: EmitTextWriter): string; + /* @internal */ writeTypePredicate(predicate: TypePredicate, enclosingDeclaration?: Node, flags?: TypeFormatFlags, writer?: EmitTextWriter): string; + /** * @deprecated Use the createX factory functions or XToY typechecker methods and `createPrinter` or the `xToString` methods instead * This will be removed in a future version. @@ -2897,7 +2917,7 @@ namespace ts { * This should be called in a loop climbing parents of the symbol, so we'll get `N`. */ /* @internal */ getAccessibleSymbolChain(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean): Symbol[] | undefined; - + /* @internal */ getTypePredicateOfSignature(signature: Signature): TypePredicate; /* @internal */ resolveExternalModuleSymbol(symbol: Symbol): Symbol; } @@ -2908,30 +2928,98 @@ namespace ts { Subtype } - export enum NodeBuilderFlags { + export const enum NodeBuilderFlags { None = 0, // Options NoTruncation = 1 << 0, // Don't truncate result WriteArrayAsGenericType = 1 << 1, // Write Array instead T[] + WriteDefaultSymbolWithoutName = 1 << 2, // Write `default`-named symbols as `default` instead of how they were written + // empty space WriteTypeArgumentsOfSignature = 1 << 5, // Write the type arguments instead of type parameters of the signature UseFullyQualifiedType = 1 << 6, // Write out the fully qualified type name (eg. Module.Type, instead of Type) + UseOnlyExternalAliasing = 1 << 7, // Only use external aliases for a symbol SuppressAnyReturnType = 1 << 8, // If the return type is any-like, don't offer a return type. WriteTypeParametersInQualifiedName = 1 << 9, + MultilineObjectLiterals = 1 << 10, // Always write object literals across multiple lines + WriteClassExpressionAsTypeLiteral = 1 << 11, // Write class {} as { new(): {} } - used for mixin declaration emit + UseTypeOfFunction = 1 << 12, // Build using typeof instead of function type literal + OmitParameterModifiers = 1 << 13, // Omit modifiers on parameters + UseAliasDefinedOutsideCurrentScope = 1 << 14, // Allow non-visible aliases // Error handling - AllowThisInObjectLiteral = 1 << 10, - AllowQualifedNameInPlaceOfIdentifier = 1 << 11, - AllowAnonymousIdentifier = 1 << 13, - AllowEmptyUnionOrIntersection = 1 << 14, - AllowEmptyTuple = 1 << 15, + AllowThisInObjectLiteral = 1 << 15, + AllowQualifedNameInPlaceOfIdentifier = 1 << 16, + AllowAnonymousIdentifier = 1 << 17, + AllowEmptyUnionOrIntersection = 1 << 18, + AllowEmptyTuple = 1 << 19, + AllowUniqueESSymbolType = 1 << 20, + AllowEmptyIndexInfoType = 1 << 21, - IgnoreErrors = AllowThisInObjectLiteral | AllowQualifedNameInPlaceOfIdentifier | AllowAnonymousIdentifier | AllowEmptyUnionOrIntersection | AllowEmptyTuple, + IgnoreErrors = AllowThisInObjectLiteral | AllowQualifedNameInPlaceOfIdentifier | AllowAnonymousIdentifier | AllowEmptyUnionOrIntersection | AllowEmptyTuple | AllowEmptyIndexInfoType, // State - InObjectTypeLiteral = 1 << 20, + InObjectTypeLiteral = 1 << 22, InTypeAlias = 1 << 23, // Writing type in type alias declaration } + // Ensure the shared flags between this and `NodeBuilderFlags` stay in alignment + export const enum TypeFormatFlags { + None = 0, + NoTruncation = 1 << 0, // Don't truncate typeToString result + WriteArrayAsGenericType = 1 << 1, // Write Array instead T[] + WriteDefaultSymbolWithoutName = 1 << 2, // Write all `defaut`-named symbols as `default` instead of their written name + // hole because there's a hole in node builder flags + WriteTypeArgumentsOfSignature = 1 << 5, // Write the type arguments instead of type parameters of the signature + UseFullyQualifiedType = 1 << 6, // Write out the fully qualified type name (eg. Module.Type, instead of Type) + // hole because `UseOnlyExternalAliasing` is here in node builder flags, but functions which take old flags use `SymbolFormatFlags` instead + SuppressAnyReturnType = 1 << 8, // If the return type is any-like, don't offer a return type. + // hole because `WriteTypeParametersInQualifiedName` is here in node builder flags, but functions which take old flags use `SymbolFormatFlags` for this instead + MultilineObjectLiterals = 1 << 10, // Always print object literals across multiple lines (only used to map into node builder flags) + WriteClassExpressionAsTypeLiteral = 1 << 11, // Write a type literal instead of (Anonymous class) + UseTypeOfFunction = 1 << 12, // Write typeof instead of function type literal + OmitParameterModifiers = 1 << 13, // Omit modifiers on parameters + UseAliasDefinedOutsideCurrentScope = 1 << 14, // For a `type T = ... ` defined in a different file, write `T` instead of its value, + // even though `T` can't be accessed in the current scope. + + // Error Handling + AllowUniqueESSymbolType = 1 << 20, // This is bit 20 to align with the same bit in `NodeBuilderFlags` + + // TypeFormatFlags exclusive + AddUndefined = 1 << 17, // Add undefined to types of initialized, non-optional parameters + WriteArrowStyleSignature = 1 << 18, // Write arrow style signature + + // State + InArrayType = 1 << 19, // Writing an array element type + InElementType = 1 << 21, // Writing an array or union element type + InFirstTypeArgument = 1 << 22, // Writing first type argument of the instantiated type + InTypeAlias = 1 << 23, // Writing type in type alias declaration + + /** @deprecated */ WriteOwnNameForAnyLike = 0, // Does nothing + + NodeBuilderFlagsMask = + NoTruncation | WriteArrayAsGenericType | WriteDefaultSymbolWithoutName | WriteTypeArgumentsOfSignature | + UseFullyQualifiedType | SuppressAnyReturnType | MultilineObjectLiterals | WriteClassExpressionAsTypeLiteral | + UseTypeOfFunction | OmitParameterModifiers | UseAliasDefinedOutsideCurrentScope | AllowUniqueESSymbolType | InTypeAlias, + } + + export const enum SymbolFormatFlags { + None = 0x00000000, + + // Write symbols's type argument if it is instantiated symbol + // eg. class C { p: T } <-- Show p as C.p here + // var a: C; + // var p = a.p; <--- Here p is property of C so show it as C.p instead of just C.p + WriteTypeParametersOrArguments = 0x00000001, + + // Use only external alias information to get the symbol name in the given context + // eg. module m { export class c { } } import x = m.c; + // When this flag is specified m.c will be used to refer to the class instead of alias symbol x + UseOnlyExternalAliasing = 0x00000002, + + // Build symbol name using any nodes needed, instead of just components of an entity name + AllowAnyNodeKind = 0x00000004, + } + /* @internal */ export interface SymbolWalker { /** Note: Return values are not ordered. */ @@ -2940,21 +3028,27 @@ namespace ts { walkSymbol(root: Symbol): { visitedTypes: ReadonlyArray, visitedSymbols: ReadonlyArray }; } + /** + * @deprecated + */ export interface SymbolDisplayBuilder { - buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildSymbolDisplay(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): void; - buildSignatureDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): void; - buildIndexSignatureDisplay(info: IndexInfo, writer: SymbolWriter, kind: IndexKind, enclosingDeclaration?: Node, globalFlags?: TypeFormatFlags, symbolStack?: Symbol[]): void; - buildParameterDisplay(parameter: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildTypeParameterDisplay(tp: TypeParameter, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildTypePredicateDisplay(predicate: TypePredicate, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildTypeParameterDisplayFromSymbol(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildDisplayForParametersAndDelimiters(thisParameter: Symbol, parameters: Symbol[], writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildDisplayForTypeParametersAndDelimiters(typeParameters: TypeParameter[], writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildReturnTypeDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildSymbolDisplay(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): void; + /** @deprecated */ buildSignatureDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): void; + /** @deprecated */ buildIndexSignatureDisplay(info: IndexInfo, writer: SymbolWriter, kind: IndexKind, enclosingDeclaration?: Node, globalFlags?: TypeFormatFlags, symbolStack?: Symbol[]): void; + /** @deprecated */ buildParameterDisplay(parameter: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildTypeParameterDisplay(tp: TypeParameter, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildTypePredicateDisplay(predicate: TypePredicate, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildTypeParameterDisplayFromSymbol(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildDisplayForParametersAndDelimiters(thisParameter: Symbol, parameters: Symbol[], writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildDisplayForTypeParametersAndDelimiters(typeParameters: TypeParameter[], writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildReturnTypeDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; } - export interface SymbolWriter { + /** + * @deprecated Migrate to other methods of generating symbol names, ex symbolToEntityName + a printer or symbolToString + */ + export interface SymbolWriter extends SymbolTracker { writeKeyword(text: string): void; writeOperator(text: string): void; writePunctuation(text: string): void; @@ -2967,50 +3061,6 @@ namespace ts { increaseIndent(): void; decreaseIndent(): void; clear(): void; - - // Called when the symbol writer encounters a symbol to write. Currently only used by the - // declaration emitter to help determine if it should patch up the final declaration file - // with import statements it previously saw (but chose not to emit). - trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): void; - reportInaccessibleThisError(): void; - reportPrivateInBaseOfClassExpression(propertyName: string): void; - reportInaccessibleUniqueSymbolError(): void; - } - - export const enum TypeFormatFlags { - None = 0, - WriteArrayAsGenericType = 1 << 0, // Write Array instead T[] - UseTypeOfFunction = 1 << 2, // Write typeof instead of function type literal - NoTruncation = 1 << 3, // Don't truncate typeToString result - WriteArrowStyleSignature = 1 << 4, // Write arrow style signature - WriteOwnNameForAnyLike = 1 << 5, // Write symbol's own name instead of 'any' for any like types (eg. unknown, __resolving__ etc) - WriteTypeArgumentsOfSignature = 1 << 6, // Write the type arguments instead of type parameters of the signature - InElementType = 1 << 7, // Writing an array or union element type - UseFullyQualifiedType = 1 << 8, // Write out the fully qualified type name (eg. Module.Type, instead of Type) - InFirstTypeArgument = 1 << 9, // Writing first type argument of the instantiated type - InTypeAlias = 1 << 10, // Writing type in type alias declaration - SuppressAnyReturnType = 1 << 12, // If the return type is any-like, don't offer a return type. - AddUndefined = 1 << 13, // Add undefined to types of initialized, non-optional parameters - WriteClassExpressionAsTypeLiteral = 1 << 14, // Write a type literal instead of (Anonymous class) - InArrayType = 1 << 15, // Writing an array element type - UseAliasDefinedOutsideCurrentScope = 1 << 16, // For a `type T = ... ` defined in a different file, write `T` instead of its value, - // even though `T` can't be accessed in the current scope. - AllowUniqueESSymbolType = 1 << 17, - } - - export const enum SymbolFormatFlags { - None = 0x00000000, - - // Write symbols's type argument if it is instantiated symbol - // eg. class C { p: T } <-- Show p as C.p here - // var a: C; - // var p = a.p; <--- Here p is property of C so show it as C.p instead of just C.p - WriteTypeParametersOrArguments = 0x00000001, - - // Use only external alias information to get the symbol name in the given context - // eg. module m { export class c { } } import x = m.c; - // When this flag is specified m.c will be used to refer to the class instead of alias symbol x - UseOnlyExternalAliasing = 0x00000002, } /* @internal */ @@ -3102,9 +3152,9 @@ namespace ts { isImplementationOfOverload(node: FunctionLikeDeclaration): boolean | undefined; isRequiredInitializedParameter(node: ParameterDeclaration): boolean; isOptionalUninitializedParameterProperty(node: ParameterDeclaration): boolean; - writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void; - writeReturnTypeOfSignatureDeclaration(signatureDeclaration: SignatureDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void; - writeTypeOfExpression(expr: Expression, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void; + writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: EmitTextWriter): void; + writeReturnTypeOfSignatureDeclaration(signatureDeclaration: SignatureDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: EmitTextWriter): void; + writeTypeOfExpression(expr: Expression, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: EmitTextWriter): void; isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags, shouldComputeAliasToMarkVisible: boolean): SymbolAccessibilityResult; isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult; // Returns the constant value this property access resolves to, or 'undefined' for a non-constant @@ -3118,7 +3168,7 @@ namespace ts { getTypeReferenceDirectivesForEntityName(name: EntityNameOrEntityNameExpression): string[]; getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): string[]; isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean; - writeLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, writer: SymbolWriter): void; + writeLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, writer: EmitTextWriter): void; getJsxFactoryEntity(): EntityName; } @@ -4765,6 +4815,10 @@ namespace ts { * collisions. */ printNode(hint: EmitHint, node: Node, sourceFile: SourceFile): string; + /** + * Prints a list of nodes using the given format flags + */ + printList(format: ListFormat, list: NodeArray, sourceFile: SourceFile): string; /** * Prints a source file as-is, without any emit transformations. */ @@ -4774,6 +4828,7 @@ namespace ts { */ printBundle(bundle: Bundle): string; /*@internal*/ writeNode(hint: EmitHint, node: Node, sourceFile: SourceFile | undefined, writer: EmitTextWriter): void; + /*@internal*/ writeList(format: ListFormat, list: NodeArray, sourceFile: SourceFile | undefined, writer: EmitTextWriter): void; /*@internal*/ writeFile(sourceFile: SourceFile, writer: EmitTextWriter): void; /*@internal*/ writeBundle(bundle: Bundle, writer: EmitTextWriter): void; } @@ -4821,7 +4876,7 @@ namespace ts { */ substituteNode?(hint: EmitHint, node: Node): Node; /*@internal*/ onEmitSourceMapOfNode?: (hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) => void; - /*@internal*/ onEmitSourceMapOfToken?: (node: Node, token: SyntaxKind, pos: number, emitCallback: (token: SyntaxKind, pos: number) => number) => number; + /*@internal*/ onEmitSourceMapOfToken?: (node: Node, token: SyntaxKind, writer: (s: string) => void, pos: number, emitCallback: (token: SyntaxKind, writer: (s: string) => void, pos: number) => number) => number; /*@internal*/ onEmitSourceMapOfPosition?: (pos: number) => void; /*@internal*/ onEmitHelpers?: (node: Node, writeLines: (text: string) => void) => void; /*@internal*/ onSetSourceFile?: (node: SourceFile) => void; @@ -4834,13 +4889,14 @@ namespace ts { export interface PrinterOptions { removeComments?: boolean; newLine?: NewLineKind; + omitTrailingSemicolon?: boolean; /*@internal*/ sourceMap?: boolean; /*@internal*/ inlineSourceMap?: boolean; /*@internal*/ extendedDiagnostics?: boolean; } - /*@internal*/ - export interface EmitTextWriter { + /* @internal */ + export interface EmitTextWriter extends SymbolTracker, SymbolWriter { write(s: string): void; writeTextOfNode(text: string, node: Node): void; writeLine(): void; @@ -4854,7 +4910,26 @@ namespace ts { getColumn(): number; getIndent(): number; isAtStartOfLine(): boolean; - reset(): void; + clear(): void; + + writeKeyword(text: string): void; + writeOperator(text: string): void; + writePunctuation(text: string): void; + writeSpace(text: string): void; + writeStringLiteral(text: string): void; + writeParameter(text: string): void; + writeProperty(text: string): void; + writeSymbol(text: string, symbol: Symbol): void; + } + + export interface SymbolTracker { + // Called when the symbol writer encounters a symbol to write. Currently only used by the + // declaration emitter to help determine if it should patch up the final declaration file + // with import statements it previously saw (but chose not to emit). + trackSymbol?(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): void; + reportInaccessibleThisError?(): void; + reportPrivateInBaseOfClassExpression?(propertyName: string): void; + reportInaccessibleUniqueSymbolError?(): void; } export interface TextSpan { @@ -4893,4 +4968,86 @@ namespace ts { export interface SyntaxList extends Node { _children: Node[]; } + + export const enum ListFormat { + None = 0, + + // Line separators + SingleLine = 0, // Prints the list on a single line (default). + MultiLine = 1 << 0, // Prints the list on multiple lines. + PreserveLines = 1 << 1, // Prints the list using line preservation if possible. + LinesMask = SingleLine | MultiLine | PreserveLines, + + // Delimiters + NotDelimited = 0, // There is no delimiter between list items (default). + BarDelimited = 1 << 2, // Each list item is space-and-bar (" |") delimited. + AmpersandDelimited = 1 << 3, // Each list item is space-and-ampersand (" &") delimited. + CommaDelimited = 1 << 4, // Each list item is comma (",") delimited. + DelimitersMask = BarDelimited | AmpersandDelimited | CommaDelimited, + + AllowTrailingComma = 1 << 5, // Write a trailing comma (",") if present. + + // Whitespace + Indented = 1 << 6, // The list should be indented. + SpaceBetweenBraces = 1 << 7, // Inserts a space after the opening brace and before the closing brace. + SpaceBetweenSiblings = 1 << 8, // Inserts a space between each sibling node. + + // Brackets/Braces + Braces = 1 << 9, // The list is surrounded by "{" and "}". + Parenthesis = 1 << 10, // The list is surrounded by "(" and ")". + AngleBrackets = 1 << 11, // The list is surrounded by "<" and ">". + SquareBrackets = 1 << 12, // The list is surrounded by "[" and "]". + BracketsMask = Braces | Parenthesis | AngleBrackets | SquareBrackets, + + OptionalIfUndefined = 1 << 13, // Do not emit brackets if the list is undefined. + OptionalIfEmpty = 1 << 14, // Do not emit brackets if the list is empty. + Optional = OptionalIfUndefined | OptionalIfEmpty, + + // Other + PreferNewLine = 1 << 15, // Prefer adding a LineTerminator between synthesized nodes. + NoTrailingNewLine = 1 << 16, // Do not emit a trailing NewLine for a MultiLine list. + NoInterveningComments = 1 << 17, // Do not emit comments between each node + + NoSpaceIfEmpty = 1 << 18, // If the literal is empty, do not add spaces between braces. + SingleElement = 1 << 19, + + // Precomputed Formats + Modifiers = SingleLine | SpaceBetweenSiblings | NoInterveningComments, + HeritageClauses = SingleLine | SpaceBetweenSiblings, + SingleLineTypeLiteralMembers = SingleLine | SpaceBetweenBraces | SpaceBetweenSiblings | Indented, + MultiLineTypeLiteralMembers = MultiLine | Indented, + + TupleTypeElements = CommaDelimited | SpaceBetweenSiblings | SingleLine | Indented, + UnionTypeConstituents = BarDelimited | SpaceBetweenSiblings | SingleLine, + IntersectionTypeConstituents = AmpersandDelimited | SpaceBetweenSiblings | SingleLine, + ObjectBindingPatternElements = SingleLine | AllowTrailingComma | SpaceBetweenBraces | CommaDelimited | SpaceBetweenSiblings | NoSpaceIfEmpty, + ArrayBindingPatternElements = SingleLine | AllowTrailingComma | CommaDelimited | SpaceBetweenSiblings | NoSpaceIfEmpty, + ObjectLiteralExpressionProperties = PreserveLines | CommaDelimited | SpaceBetweenSiblings | SpaceBetweenBraces | Indented | Braces | NoSpaceIfEmpty, + ArrayLiteralExpressionElements = PreserveLines | CommaDelimited | SpaceBetweenSiblings | AllowTrailingComma | Indented | SquareBrackets, + CommaListElements = CommaDelimited | SpaceBetweenSiblings | SingleLine, + CallExpressionArguments = CommaDelimited | SpaceBetweenSiblings | SingleLine | Parenthesis, + NewExpressionArguments = CommaDelimited | SpaceBetweenSiblings | SingleLine | Parenthesis | OptionalIfUndefined, + TemplateExpressionSpans = SingleLine | NoInterveningComments, + SingleLineBlockStatements = SpaceBetweenBraces | SpaceBetweenSiblings | SingleLine, + MultiLineBlockStatements = Indented | MultiLine, + VariableDeclarationList = CommaDelimited | SpaceBetweenSiblings | SingleLine, + SingleLineFunctionBodyStatements = SingleLine | SpaceBetweenSiblings | SpaceBetweenBraces, + MultiLineFunctionBodyStatements = MultiLine, + ClassHeritageClauses = SingleLine | SpaceBetweenSiblings, + ClassMembers = Indented | MultiLine, + InterfaceMembers = Indented | MultiLine, + EnumMembers = CommaDelimited | Indented | MultiLine, + CaseBlockClauses = Indented | MultiLine, + NamedImportsOrExportsElements = CommaDelimited | SpaceBetweenSiblings | AllowTrailingComma | SingleLine | SpaceBetweenBraces, + JsxElementOrFragmentChildren = SingleLine | NoInterveningComments, + JsxElementAttributes = SingleLine | SpaceBetweenSiblings | NoInterveningComments, + CaseOrDefaultClauseStatements = Indented | MultiLine | NoTrailingNewLine | OptionalIfEmpty, + HeritageClauseTypes = CommaDelimited | SpaceBetweenSiblings | SingleLine, + SourceFileStatements = MultiLine | NoTrailingNewLine, + Decorators = MultiLine | Optional, + TypeArguments = CommaDelimited | SpaceBetweenSiblings | SingleLine | AngleBrackets | Optional, + TypeParameters = CommaDelimited | SpaceBetweenSiblings | SingleLine | AngleBrackets | Optional, + Parameters = CommaDelimited | SpaceBetweenSiblings | SingleLine | Parenthesis, + IndexSignatureParameters = CommaDelimited | SpaceBetweenSiblings | SingleLine | Indented | SquareBrackets, + } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 8304320b301..796ed35a861 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -29,26 +29,31 @@ namespace ts { return undefined; } - export interface StringSymbolWriter extends SymbolWriter { - string(): string; - } - const stringWriter = createSingleLineStringWriter(); - function createSingleLineStringWriter(): StringSymbolWriter { + function createSingleLineStringWriter(): EmitTextWriter { let str = ""; const writeText: (text: string) => void = text => str += text; return { - string: () => str, + getText: () => str, + write: writeText, + rawWrite: writeText, + writeTextOfNode: writeText, writeKeyword: writeText, writeOperator: writeText, writePunctuation: writeText, writeSpace: writeText, writeStringLiteral: writeText, + writeLiteral: writeText, writeParameter: writeText, writeProperty: writeText, writeSymbol: writeText, + getTextPos: () => str.length, + getLine: () => 0, + getColumn: () => 0, + getIndent: () => 0, + isAtStartOfLine: () => false, // Completely ignore indentation for string writers. And map newlines to // a single space. @@ -63,11 +68,11 @@ namespace ts { }; } - export function usingSingleLineStringWriter(action: (writer: StringSymbolWriter) => void): string { - const oldString = stringWriter.string(); + export function usingSingleLineStringWriter(action: (writer: EmitTextWriter) => void): string { + const oldString = stringWriter.getText(); try { action(stringWriter); - return stringWriter.string(); + return stringWriter.getText(); } finally { stringWriter.clear(); @@ -2606,7 +2611,19 @@ namespace ts { getColumn: () => lineStart ? indent * getIndentSize() + 1 : output.length - linePos + 1, getText: () => output, isAtStartOfLine: () => lineStart, - reset + clear: reset, + reportInaccessibleThisError: noop, + reportPrivateInBaseOfClassExpression: noop, + reportInaccessibleUniqueSymbolError: noop, + trackSymbol: noop, + writeKeyword: write, + writeOperator: write, + writeParameter: write, + writeProperty: write, + writePunctuation: write, + writeSpace: write, + writeStringLiteral: write, + writeSymbol: write }; } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 2d678eb0eff..12b5d4f1b07 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -3,6 +3,8 @@ /// namespace ts { + const isTypeNodeOrTypeParameterDeclaration = or(isTypeNode, isTypeParameterDeclaration); + /** * Visits a Node using the supplied visitor, possibly returning a new Node in its place. * @@ -222,7 +224,7 @@ namespace ts { // Names case SyntaxKind.Identifier: - return updateIdentifier(node, nodesVisitor((node).typeArguments, visitor, isTypeNode)); + return updateIdentifier(node, nodesVisitor((node).typeArguments, visitor, isTypeNodeOrTypeParameterDeclaration)); case SyntaxKind.QualifiedName: return updateQualifiedName(node, diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 95d85bc5aa0..6f6b2f3c61e 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -229,13 +229,13 @@ namespace ts.codefix { } } - function getTypeAccessiblityWriter(checker: TypeChecker): StringSymbolWriter { + function getTypeAccessiblityWriter(checker: TypeChecker): EmitTextWriter { let str = ""; let typeIsAccessible = true; const writeText: (text: string) => void = text => str += text; return { - string: () => typeIsAccessible ? str : undefined, + getText: () => typeIsAccessible ? str : undefined, writeKeyword: writeText, writeOperator: writeText, writePunctuation: writeText, @@ -244,6 +244,15 @@ namespace ts.codefix { writeParameter: writeText, writeProperty: writeText, writeSymbol: writeText, + write: writeText, + writeTextOfNode: writeText, + rawWrite: writeText, + writeLiteral: writeText, + getTextPos: () => 0, + getLine: () => 0, + getColumn: () => 0, + getIndent: () => 0, + isAtStartOfLine: () => false, writeLine: () => writeText(" "), increaseIndent: noop, decreaseIndent: noop, @@ -261,8 +270,8 @@ namespace ts.codefix { function typeToString(type: Type, enclosingDeclaration: Declaration, checker: TypeChecker): string { const writer = getTypeAccessiblityWriter(checker); - checker.getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration); - return writer.string(); + checker.writeType(type, enclosingDeclaration, /*flags*/ undefined, writer); + return writer.getText(); } namespace InferFromReference { diff --git a/src/services/services.ts b/src/services/services.ts index 90e9b64a1df..4236416fbb3 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -883,7 +883,8 @@ namespace ts { scriptKind: ScriptKind; } - export interface DisplayPartsSymbolWriter extends SymbolWriter { + /* @internal */ + export interface DisplayPartsSymbolWriter extends EmitTextWriter { displayParts(): SymbolDisplayPart[]; } diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index f5338db954b..734912b3ffc 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -360,6 +360,7 @@ namespace ts.SignatureHelp { const callTarget = getInvokedExpression(invocation); const callTargetSymbol = typeChecker.getSymbolAtLocation(callTarget); const callTargetDisplayParts = callTargetSymbol && symbolToDisplayParts(typeChecker, callTargetSymbol, /*enclosingDeclaration*/ undefined, /*meaning*/ undefined); + const printer = createPrinter({ removeComments: true }); const items: SignatureHelpItem[] = map(candidates, candidateSignature => { let signatureHelpParameters: SignatureHelpParameter[]; const prefixDisplayParts: SymbolDisplayPart[] = []; @@ -376,14 +377,22 @@ namespace ts.SignatureHelp { const typeParameters = (candidateSignature.target || candidateSignature).typeParameters; signatureHelpParameters = typeParameters && typeParameters.length > 0 ? map(typeParameters, createSignatureHelpParameterForTypeParameter) : emptyArray; suffixDisplayParts.push(punctuationPart(SyntaxKind.GreaterThanToken)); - const parameterParts = mapToDisplayParts(writer => - typeChecker.getSymbolDisplayBuilder().buildDisplayForParametersAndDelimiters(candidateSignature.thisParameter, candidateSignature.parameters, writer, invocation)); + const parameterParts = mapToDisplayParts(writer => { + const flags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors; + const thisParameter = candidateSignature.thisParameter ? [typeChecker.symbolToParameterDeclaration(candidateSignature.thisParameter, invocation, flags)] : []; + const params = createNodeArray([...thisParameter, ...map(candidateSignature.parameters, param => typeChecker.symbolToParameterDeclaration(param, invocation, flags))]); + printer.writeList(ListFormat.CallExpressionArguments, params, getSourceFileOfNode(getParseTreeNode(invocation)), writer); + }); addRange(suffixDisplayParts, parameterParts); } else { isVariadic = candidateSignature.hasRestParameter; - const typeParameterParts = mapToDisplayParts(writer => - typeChecker.getSymbolDisplayBuilder().buildDisplayForTypeParametersAndDelimiters(candidateSignature.typeParameters, writer, invocation)); + const typeParameterParts = mapToDisplayParts(writer => { + if (candidateSignature.typeParameters && candidateSignature.typeParameters.length) { + const args = createNodeArray(map(candidateSignature.typeParameters, p => typeChecker.typeParameterToDeclaration(p, invocation))); + printer.writeList(ListFormat.TypeParameters, args, getSourceFileOfNode(getParseTreeNode(invocation)), writer); + } + }); addRange(prefixDisplayParts, typeParameterParts); prefixDisplayParts.push(punctuationPart(SyntaxKind.OpenParenToken)); @@ -391,8 +400,17 @@ namespace ts.SignatureHelp { suffixDisplayParts.push(punctuationPart(SyntaxKind.CloseParenToken)); } - const returnTypeParts = mapToDisplayParts(writer => - typeChecker.getSymbolDisplayBuilder().buildReturnTypeDisplay(candidateSignature, writer, invocation)); + const returnTypeParts = mapToDisplayParts(writer => { + writer.writePunctuation(":"); + writer.writeSpace(" "); + const predicate = typeChecker.getTypePredicateOfSignature(candidateSignature); + if (predicate) { + typeChecker.writeTypePredicate(predicate, invocation, /*flags*/ undefined, writer); + } + else { + typeChecker.writeType(typeChecker.getReturnTypeOfSignature(candidateSignature), invocation, /*flags*/ undefined, writer); + } + }); addRange(suffixDisplayParts, returnTypeParts); return { @@ -416,8 +434,10 @@ namespace ts.SignatureHelp { return { items, applicableSpan, selectedItemIndex, argumentIndex, argumentCount }; function createSignatureHelpParameterForParameter(parameter: Symbol): SignatureHelpParameter { - const displayParts = mapToDisplayParts(writer => - typeChecker.getSymbolDisplayBuilder().buildParameterDisplay(parameter, writer, invocation)); + const displayParts = mapToDisplayParts(writer => { + const param = typeChecker.symbolToParameterDeclaration(parameter, invocation, NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors); + printer.writeNode(EmitHint.Unspecified, param, getSourceFileOfNode(getParseTreeNode(invocation)), writer); + }); return { name: parameter.name, @@ -428,8 +448,10 @@ namespace ts.SignatureHelp { } function createSignatureHelpParameterForTypeParameter(typeParameter: TypeParameter): SignatureHelpParameter { - const displayParts = mapToDisplayParts(writer => - typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplay(typeParameter, writer, invocation)); + const displayParts = mapToDisplayParts(writer => { + const param = typeChecker.typeParameterToDeclaration(typeParameter, invocation); + printer.writeNode(EmitHint.Unspecified, param, getSourceFileOfNode(getParseTreeNode(invocation)), writer); + }); return { name: typeParameter.symbol.name, diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index 6f979f1721b..9ec99f6ccaf 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -120,6 +120,7 @@ namespace ts.SymbolDisplay { let hasAddedSymbolInfo: boolean; const isThisExpression = location.kind === SyntaxKind.ThisKeyword && isExpression(location); let type: Type; + let printer: Printer; let documentationFromAlias: SymbolDisplayPart[]; // Class at constructor site need to be shown as constructor apart from property,method, vars @@ -198,7 +199,7 @@ namespace ts.SymbolDisplay { displayParts.push(punctuationPart(SyntaxKind.ColonToken)); displayParts.push(spacePart()); if (!(type.flags & TypeFlags.Object && (type).objectFlags & ObjectFlags.Anonymous) && type.symbol) { - addRange(displayParts, symbolToDisplayParts(typeChecker, type.symbol, enclosingDeclaration, /*meaning*/ undefined, SymbolFormatFlags.WriteTypeParametersOrArguments)); + addRange(displayParts, symbolToDisplayParts(typeChecker, type.symbol, enclosingDeclaration, /*meaning*/ undefined, SymbolFormatFlags.AllowAnyNodeKind | SymbolFormatFlags.WriteTypeParametersOrArguments)); displayParts.push(lineBreakPart()); } if (useConstructSignatures) { @@ -450,7 +451,8 @@ namespace ts.SymbolDisplay { // If the type is type parameter, format it specially if (type.symbol && type.symbol.flags & SymbolFlags.TypeParameter) { const typeParameterParts = mapToDisplayParts(writer => { - typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplay(type, writer, enclosingDeclaration); + const param = typeChecker.typeParameterToDeclaration(type as TypeParameter, enclosingDeclaration); + getPrinter().writeNode(EmitHint.Unspecified, param, getSourceFileOfNode(getParseTreeNode(enclosingDeclaration)), writer); }); addRange(displayParts, typeParameterParts); } @@ -510,6 +512,13 @@ namespace ts.SymbolDisplay { return { displayParts, documentation, symbolKind, tags }; + function getPrinter() { + if (!printer) { + printer = createPrinter({ removeComments: true }); + } + return printer; + } + function prefixNextMeaning() { if (displayParts.length) { displayParts.push(lineBreakPart()); @@ -535,7 +544,7 @@ namespace ts.SymbolDisplay { symbolToDisplay = alias; } const fullSymbolDisplayParts = symbolToDisplayParts(typeChecker, symbolToDisplay, enclosingDeclaration || sourceFile, /*meaning*/ undefined, - SymbolFormatFlags.WriteTypeParametersOrArguments | SymbolFormatFlags.UseOnlyExternalAliasing); + SymbolFormatFlags.WriteTypeParametersOrArguments | SymbolFormatFlags.UseOnlyExternalAliasing | SymbolFormatFlags.AllowAnyNodeKind); addRange(displayParts, fullSymbolDisplayParts); } @@ -584,7 +593,8 @@ namespace ts.SymbolDisplay { function writeTypeParametersOfSymbol(symbol: Symbol, enclosingDeclaration: Node) { const typeParameterParts = mapToDisplayParts(writer => { - typeChecker.getSymbolDisplayBuilder().buildTypeParameterDisplayFromSymbol(symbol, writer, enclosingDeclaration); + const params = typeChecker.symbolToTypeParameterDeclarations(symbol, enclosingDeclaration); + getPrinter().writeList(ListFormat.TypeParameters, params, getSourceFileOfNode(getParseTreeNode(enclosingDeclaration)), writer); }); addRange(displayParts, typeParameterParts); } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index b0efef44642..467ba6735ca 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -810,6 +810,38 @@ namespace ts.textChanges { this.writer.write(s); this.setLastNonTriviaPosition(s, /*force*/ false); } + writeKeyword(s: string): void { + this.writer.writeKeyword(s); + this.setLastNonTriviaPosition(s, /*force*/ false); + } + writeOperator(s: string): void { + this.writer.writeOperator(s); + this.setLastNonTriviaPosition(s, /*force*/ false); + } + writePunctuation(s: string): void { + this.writer.writePunctuation(s); + this.setLastNonTriviaPosition(s, /*force*/ false); + } + writeParameter(s: string): void { + this.writer.writeParameter(s); + this.setLastNonTriviaPosition(s, /*force*/ false); + } + writeProperty(s: string): void { + this.writer.writeProperty(s); + this.setLastNonTriviaPosition(s, /*force*/ false); + } + writeSpace(s: string): void { + this.writer.writeSpace(s); + this.setLastNonTriviaPosition(s, /*force*/ false); + } + writeStringLiteral(s: string): void { + this.writer.writeStringLiteral(s); + this.setLastNonTriviaPosition(s, /*force*/ false); + } + writeSymbol(s: string, sym: Symbol): void { + this.writer.writeSymbol(s, sym); + this.setLastNonTriviaPosition(s, /*force*/ false); + } writeTextOfNode(text: string, node: Node): void { this.writer.writeTextOfNode(text, node); } @@ -848,8 +880,8 @@ namespace ts.textChanges { isAtStartOfLine(): boolean { return this.writer.isAtStartOfLine(); } - reset(): void { - this.writer.reset(); + clear(): void { + this.writer.clear(); this.lastNonTriviaPosition = 0; } } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 9562def31f5..c5694625493 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1127,6 +1127,7 @@ namespace ts { let indent: number; resetWriter(); + const unknownWrite = (text: string) => writeKind(text, SymbolDisplayPartKind.text); return { displayParts: () => displayParts, writeKeyword: text => writeKind(text, SymbolDisplayPartKind.keyword), @@ -1136,8 +1137,18 @@ namespace ts { writeStringLiteral: text => writeKind(text, SymbolDisplayPartKind.stringLiteral), writeParameter: text => writeKind(text, SymbolDisplayPartKind.parameterName), writeProperty: text => writeKind(text, SymbolDisplayPartKind.propertyName), + writeLiteral: text => writeKind(text, SymbolDisplayPartKind.stringLiteral), writeSymbol, writeLine, + write: unknownWrite, + writeTextOfNode: unknownWrite, + getText: () => "", + getTextPos: () => 0, + getColumn: () => 0, + getLine: () => 0, + isAtStartOfLine: () => false, + rawWrite: notImplemented, + getIndent: () => indent, increaseIndent: () => { indent++; }, decreaseIndent: () => { indent--; }, clear: resetWriter, @@ -1249,6 +1260,7 @@ namespace ts { return displayPart("\n", SymbolDisplayPartKind.lineBreak); } + /* @internal */ export function mapToDisplayParts(writeDisplayParts: (writer: DisplayPartsSymbolWriter) => void): SymbolDisplayPart[] { try { writeDisplayParts(displayPartWriter); @@ -1261,20 +1273,20 @@ namespace ts { export function typeToDisplayParts(typechecker: TypeChecker, type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): SymbolDisplayPart[] { return mapToDisplayParts(writer => { - typechecker.getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration, flags); + typechecker.writeType(type, enclosingDeclaration, flags | TypeFormatFlags.MultilineObjectLiterals, writer); }); } export function symbolToDisplayParts(typeChecker: TypeChecker, symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): SymbolDisplayPart[] { return mapToDisplayParts(writer => { - typeChecker.getSymbolDisplayBuilder().buildSymbolDisplay(symbol, writer, enclosingDeclaration, meaning, flags); + typeChecker.writeSymbol(symbol, enclosingDeclaration, meaning, flags, writer); }); } export function signatureToDisplayParts(typechecker: TypeChecker, signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags): SymbolDisplayPart[] { - flags |= TypeFormatFlags.UseAliasDefinedOutsideCurrentScope; + flags |= TypeFormatFlags.UseAliasDefinedOutsideCurrentScope | TypeFormatFlags.MultilineObjectLiterals | TypeFormatFlags.WriteTypeArgumentsOfSignature | TypeFormatFlags.OmitParameterModifiers; return mapToDisplayParts(writer => { - typechecker.getSymbolDisplayBuilder().buildSignatureDisplay(signature, writer, enclosingDeclaration, flags); + typechecker.writeSignature(signature, enclosingDeclaration, flags, /*signatureKind*/ undefined, writer); }); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 3eabea4435a..bee3d4d2637 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -639,7 +639,7 @@ declare namespace ts { body?: Block | Expression; } type FunctionLikeDeclaration = FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | FunctionExpression | ArrowFunction; - type FunctionLike = FunctionLikeDeclaration | FunctionTypeNode | ConstructorTypeNode | IndexSignatureDeclaration | MethodSignature | ConstructSignatureDeclaration | CallSignatureDeclaration; + type FunctionLike = FunctionLikeDeclaration | FunctionTypeNode | ConstructorTypeNode | IndexSignatureDeclaration | MethodSignature | ConstructSignatureDeclaration | CallSignatureDeclaration | JSDocFunctionType; interface FunctionDeclaration extends FunctionLikeDeclarationBase, DeclarationStatement { kind: SyntaxKind.FunctionDeclaration; name?: Identifier; @@ -1729,6 +1729,16 @@ declare namespace ts { signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): SignatureDeclaration; /** Note that the resulting nodes cannot be checked. */ indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): IndexSignatureDeclaration; + /** Note that the resulting nodes cannot be checked. */ + symbolToEntityName(symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): EntityName; + /** Note that the resulting nodes cannot be checked. */ + symbolToExpression(symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): Expression; + /** Note that the resulting nodes cannot be checked. */ + symbolToTypeParameterDeclarations(symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): NodeArray | undefined; + /** Note that the resulting nodes cannot be checked. */ + symbolToParameterDeclaration(symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): ParameterDeclaration; + /** Note that the resulting nodes cannot be checked. */ + typeParameterToDeclaration(parameter: TypeParameter, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): TypeParameterDeclaration; getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; getSymbolAtLocation(node: Node): Symbol | undefined; getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: string): Symbol[]; @@ -1748,7 +1758,8 @@ declare namespace ts { getTypeFromTypeNode(node: TypeNode): Type; signatureToString(signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): string; typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string; - symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): string; + symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): string; + typePredicateToString(predicate: TypePredicate, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string; /** * @deprecated Use the createX factory functions or XToY typechecker methods and `createPrinter` or the `xToString` methods instead * This will be removed in a future version. @@ -1788,33 +1799,77 @@ declare namespace ts { None = 0, NoTruncation = 1, WriteArrayAsGenericType = 2, + WriteDefaultSymbolWithoutName = 4, + WriteTypeArgumentsOfSignature = 32, + UseFullyQualifiedType = 64, + UseOnlyExternalAliasing = 128, + SuppressAnyReturnType = 256, + WriteTypeParametersInQualifiedName = 512, + MultilineObjectLiterals = 1024, + WriteClassExpressionAsTypeLiteral = 2048, + UseTypeOfFunction = 4096, + OmitParameterModifiers = 8192, + UseAliasDefinedOutsideCurrentScope = 16384, + AllowThisInObjectLiteral = 32768, + AllowQualifedNameInPlaceOfIdentifier = 65536, + AllowAnonymousIdentifier = 131072, + AllowEmptyUnionOrIntersection = 262144, + AllowEmptyTuple = 524288, + AllowUniqueESSymbolType = 1048576, + AllowEmptyIndexInfoType = 2097152, + IgnoreErrors = 3112960, + InObjectTypeLiteral = 4194304, + InTypeAlias = 8388608, + } + enum TypeFormatFlags { + None = 0, + NoTruncation = 1, + WriteArrayAsGenericType = 2, + WriteDefaultSymbolWithoutName = 4, WriteTypeArgumentsOfSignature = 32, UseFullyQualifiedType = 64, SuppressAnyReturnType = 256, - WriteTypeParametersInQualifiedName = 512, - AllowThisInObjectLiteral = 1024, - AllowQualifedNameInPlaceOfIdentifier = 2048, - AllowAnonymousIdentifier = 8192, - AllowEmptyUnionOrIntersection = 16384, - AllowEmptyTuple = 32768, - IgnoreErrors = 60416, - InObjectTypeLiteral = 1048576, + MultilineObjectLiterals = 1024, + WriteClassExpressionAsTypeLiteral = 2048, + UseTypeOfFunction = 4096, + OmitParameterModifiers = 8192, + UseAliasDefinedOutsideCurrentScope = 16384, + AllowUniqueESSymbolType = 1048576, + AddUndefined = 131072, + WriteArrowStyleSignature = 262144, + InArrayType = 524288, + InElementType = 2097152, + InFirstTypeArgument = 4194304, InTypeAlias = 8388608, + /** @deprecated */ WriteOwnNameForAnyLike = 0, + NodeBuilderFlagsMask = 9469287, } + enum SymbolFormatFlags { + None = 0, + WriteTypeParametersOrArguments = 1, + UseOnlyExternalAliasing = 2, + AllowAnyNodeKind = 4, + } + /** + * @deprecated + */ interface SymbolDisplayBuilder { - buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildSymbolDisplay(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): void; - buildSignatureDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): void; - buildIndexSignatureDisplay(info: IndexInfo, writer: SymbolWriter, kind: IndexKind, enclosingDeclaration?: Node, globalFlags?: TypeFormatFlags, symbolStack?: Symbol[]): void; - buildParameterDisplay(parameter: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildTypeParameterDisplay(tp: TypeParameter, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildTypePredicateDisplay(predicate: TypePredicate, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildTypeParameterDisplayFromSymbol(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildDisplayForParametersAndDelimiters(thisParameter: Symbol, parameters: Symbol[], writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildDisplayForTypeParametersAndDelimiters(typeParameters: TypeParameter[], writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildReturnTypeDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildSymbolDisplay(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): void; + /** @deprecated */ buildSignatureDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): void; + /** @deprecated */ buildIndexSignatureDisplay(info: IndexInfo, writer: SymbolWriter, kind: IndexKind, enclosingDeclaration?: Node, globalFlags?: TypeFormatFlags, symbolStack?: Symbol[]): void; + /** @deprecated */ buildParameterDisplay(parameter: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildTypeParameterDisplay(tp: TypeParameter, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildTypePredicateDisplay(predicate: TypePredicate, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildTypeParameterDisplayFromSymbol(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildDisplayForParametersAndDelimiters(thisParameter: Symbol, parameters: Symbol[], writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildDisplayForTypeParametersAndDelimiters(typeParameters: TypeParameter[], writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildReturnTypeDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; } - interface SymbolWriter { + /** + * @deprecated Migrate to other methods of generating symbol names, ex symbolToEntityName + a printer or symbolToString + */ + interface SymbolWriter extends SymbolTracker { writeKeyword(text: string): void; writeOperator(text: string): void; writePunctuation(text: string): void; @@ -1827,34 +1882,6 @@ declare namespace ts { increaseIndent(): void; decreaseIndent(): void; clear(): void; - trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): void; - reportInaccessibleThisError(): void; - reportPrivateInBaseOfClassExpression(propertyName: string): void; - reportInaccessibleUniqueSymbolError(): void; - } - enum TypeFormatFlags { - None = 0, - WriteArrayAsGenericType = 1, - UseTypeOfFunction = 4, - NoTruncation = 8, - WriteArrowStyleSignature = 16, - WriteOwnNameForAnyLike = 32, - WriteTypeArgumentsOfSignature = 64, - InElementType = 128, - UseFullyQualifiedType = 256, - InFirstTypeArgument = 512, - InTypeAlias = 1024, - SuppressAnyReturnType = 4096, - AddUndefined = 8192, - WriteClassExpressionAsTypeLiteral = 16384, - InArrayType = 32768, - UseAliasDefinedOutsideCurrentScope = 65536, - AllowUniqueESSymbolType = 131072, - } - enum SymbolFormatFlags { - None = 0, - WriteTypeParametersOrArguments = 1, - UseOnlyExternalAliasing = 2, } enum TypePredicateKind { This = 0, @@ -2634,6 +2661,10 @@ declare namespace ts { * collisions. */ printNode(hint: EmitHint, node: Node, sourceFile: SourceFile): string; + /** + * Prints a list of nodes using the given format flags + */ + printList(format: ListFormat, list: NodeArray, sourceFile: SourceFile): string; /** * Prints a source file as-is, without any emit transformations. */ @@ -2689,6 +2720,13 @@ declare namespace ts { interface PrinterOptions { removeComments?: boolean; newLine?: NewLineKind; + omitTrailingSemicolon?: boolean; + } + interface SymbolTracker { + trackSymbol?(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): void; + reportInaccessibleThisError?(): void; + reportPrivateInBaseOfClassExpression?(propertyName: string): void; + reportInaccessibleUniqueSymbolError?(): void; } interface TextSpan { start: number; @@ -2701,6 +2739,71 @@ declare namespace ts { interface SyntaxList extends Node { _children: Node[]; } + enum ListFormat { + None = 0, + SingleLine = 0, + MultiLine = 1, + PreserveLines = 2, + LinesMask = 3, + NotDelimited = 0, + BarDelimited = 4, + AmpersandDelimited = 8, + CommaDelimited = 16, + DelimitersMask = 28, + AllowTrailingComma = 32, + Indented = 64, + SpaceBetweenBraces = 128, + SpaceBetweenSiblings = 256, + Braces = 512, + Parenthesis = 1024, + AngleBrackets = 2048, + SquareBrackets = 4096, + BracketsMask = 7680, + OptionalIfUndefined = 8192, + OptionalIfEmpty = 16384, + Optional = 24576, + PreferNewLine = 32768, + NoTrailingNewLine = 65536, + NoInterveningComments = 131072, + NoSpaceIfEmpty = 262144, + SingleElement = 524288, + Modifiers = 131328, + HeritageClauses = 256, + SingleLineTypeLiteralMembers = 448, + MultiLineTypeLiteralMembers = 65, + TupleTypeElements = 336, + UnionTypeConstituents = 260, + IntersectionTypeConstituents = 264, + ObjectBindingPatternElements = 262576, + ArrayBindingPatternElements = 262448, + ObjectLiteralExpressionProperties = 263122, + ArrayLiteralExpressionElements = 4466, + CommaListElements = 272, + CallExpressionArguments = 1296, + NewExpressionArguments = 9488, + TemplateExpressionSpans = 131072, + SingleLineBlockStatements = 384, + MultiLineBlockStatements = 65, + VariableDeclarationList = 272, + SingleLineFunctionBodyStatements = 384, + MultiLineFunctionBodyStatements = 1, + ClassHeritageClauses = 256, + ClassMembers = 65, + InterfaceMembers = 65, + EnumMembers = 81, + CaseBlockClauses = 65, + NamedImportsOrExportsElements = 432, + JsxElementOrFragmentChildren = 131072, + JsxElementAttributes = 131328, + CaseOrDefaultClauseStatements = 81985, + HeritageClauseTypes = 272, + SourceFileStatements = 65537, + Decorators = 24577, + TypeArguments = 26896, + TypeParameters = 26896, + Parameters = 1296, + IndexSignatureParameters = 4432, + } } declare namespace ts { const versionMajorMinor = "2.7"; @@ -3296,7 +3399,7 @@ declare namespace ts { function createLiteral(value: string | number | boolean): PrimaryExpression; function createNumericLiteral(value: string): NumericLiteral; function createIdentifier(text: string): Identifier; - function updateIdentifier(node: Identifier, typeArguments: NodeArray | undefined): Identifier; + function updateIdentifier(node: Identifier): Identifier; /** Create a unique temporary variable. */ function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined): Identifier; /** Create a unique temporary variable for use in a loop. */ @@ -4661,9 +4764,6 @@ declare namespace ts { declare namespace ts { /** The version of the language service API */ const servicesVersion = "0.7"; - interface DisplayPartsSymbolWriter extends SymbolWriter { - displayParts(): SymbolDisplayPart[]; - } function toEditorSettings(options: EditorOptions | EditorSettings): EditorSettings; function displayPartsToString(displayParts: SymbolDisplayPart[]): string; function getDefaultCompilerOptions(): CompilerOptions; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 630b7a08a28..09db501300b 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -639,7 +639,7 @@ declare namespace ts { body?: Block | Expression; } type FunctionLikeDeclaration = FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | FunctionExpression | ArrowFunction; - type FunctionLike = FunctionLikeDeclaration | FunctionTypeNode | ConstructorTypeNode | IndexSignatureDeclaration | MethodSignature | ConstructSignatureDeclaration | CallSignatureDeclaration; + type FunctionLike = FunctionLikeDeclaration | FunctionTypeNode | ConstructorTypeNode | IndexSignatureDeclaration | MethodSignature | ConstructSignatureDeclaration | CallSignatureDeclaration | JSDocFunctionType; interface FunctionDeclaration extends FunctionLikeDeclarationBase, DeclarationStatement { kind: SyntaxKind.FunctionDeclaration; name?: Identifier; @@ -1729,6 +1729,16 @@ declare namespace ts { signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): SignatureDeclaration; /** Note that the resulting nodes cannot be checked. */ indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): IndexSignatureDeclaration; + /** Note that the resulting nodes cannot be checked. */ + symbolToEntityName(symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): EntityName; + /** Note that the resulting nodes cannot be checked. */ + symbolToExpression(symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): Expression; + /** Note that the resulting nodes cannot be checked. */ + symbolToTypeParameterDeclarations(symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): NodeArray | undefined; + /** Note that the resulting nodes cannot be checked. */ + symbolToParameterDeclaration(symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): ParameterDeclaration; + /** Note that the resulting nodes cannot be checked. */ + typeParameterToDeclaration(parameter: TypeParameter, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): TypeParameterDeclaration; getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; getSymbolAtLocation(node: Node): Symbol | undefined; getSymbolsOfParameterPropertyDeclaration(parameter: ParameterDeclaration, parameterName: string): Symbol[]; @@ -1748,7 +1758,8 @@ declare namespace ts { getTypeFromTypeNode(node: TypeNode): Type; signatureToString(signature: Signature, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): string; typeToString(type: Type, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string; - symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): string; + symbolToString(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): string; + typePredicateToString(predicate: TypePredicate, enclosingDeclaration?: Node, flags?: TypeFormatFlags): string; /** * @deprecated Use the createX factory functions or XToY typechecker methods and `createPrinter` or the `xToString` methods instead * This will be removed in a future version. @@ -1788,33 +1799,77 @@ declare namespace ts { None = 0, NoTruncation = 1, WriteArrayAsGenericType = 2, + WriteDefaultSymbolWithoutName = 4, + WriteTypeArgumentsOfSignature = 32, + UseFullyQualifiedType = 64, + UseOnlyExternalAliasing = 128, + SuppressAnyReturnType = 256, + WriteTypeParametersInQualifiedName = 512, + MultilineObjectLiterals = 1024, + WriteClassExpressionAsTypeLiteral = 2048, + UseTypeOfFunction = 4096, + OmitParameterModifiers = 8192, + UseAliasDefinedOutsideCurrentScope = 16384, + AllowThisInObjectLiteral = 32768, + AllowQualifedNameInPlaceOfIdentifier = 65536, + AllowAnonymousIdentifier = 131072, + AllowEmptyUnionOrIntersection = 262144, + AllowEmptyTuple = 524288, + AllowUniqueESSymbolType = 1048576, + AllowEmptyIndexInfoType = 2097152, + IgnoreErrors = 3112960, + InObjectTypeLiteral = 4194304, + InTypeAlias = 8388608, + } + enum TypeFormatFlags { + None = 0, + NoTruncation = 1, + WriteArrayAsGenericType = 2, + WriteDefaultSymbolWithoutName = 4, WriteTypeArgumentsOfSignature = 32, UseFullyQualifiedType = 64, SuppressAnyReturnType = 256, - WriteTypeParametersInQualifiedName = 512, - AllowThisInObjectLiteral = 1024, - AllowQualifedNameInPlaceOfIdentifier = 2048, - AllowAnonymousIdentifier = 8192, - AllowEmptyUnionOrIntersection = 16384, - AllowEmptyTuple = 32768, - IgnoreErrors = 60416, - InObjectTypeLiteral = 1048576, + MultilineObjectLiterals = 1024, + WriteClassExpressionAsTypeLiteral = 2048, + UseTypeOfFunction = 4096, + OmitParameterModifiers = 8192, + UseAliasDefinedOutsideCurrentScope = 16384, + AllowUniqueESSymbolType = 1048576, + AddUndefined = 131072, + WriteArrowStyleSignature = 262144, + InArrayType = 524288, + InElementType = 2097152, + InFirstTypeArgument = 4194304, InTypeAlias = 8388608, + /** @deprecated */ WriteOwnNameForAnyLike = 0, + NodeBuilderFlagsMask = 9469287, } + enum SymbolFormatFlags { + None = 0, + WriteTypeParametersOrArguments = 1, + UseOnlyExternalAliasing = 2, + AllowAnyNodeKind = 4, + } + /** + * @deprecated + */ interface SymbolDisplayBuilder { - buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildSymbolDisplay(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): void; - buildSignatureDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): void; - buildIndexSignatureDisplay(info: IndexInfo, writer: SymbolWriter, kind: IndexKind, enclosingDeclaration?: Node, globalFlags?: TypeFormatFlags, symbolStack?: Symbol[]): void; - buildParameterDisplay(parameter: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildTypeParameterDisplay(tp: TypeParameter, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildTypePredicateDisplay(predicate: TypePredicate, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildTypeParameterDisplayFromSymbol(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildDisplayForParametersAndDelimiters(thisParameter: Symbol, parameters: Symbol[], writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildDisplayForTypeParametersAndDelimiters(typeParameters: TypeParameter[], writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; - buildReturnTypeDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildSymbolDisplay(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): void; + /** @deprecated */ buildSignatureDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, kind?: SignatureKind): void; + /** @deprecated */ buildIndexSignatureDisplay(info: IndexInfo, writer: SymbolWriter, kind: IndexKind, enclosingDeclaration?: Node, globalFlags?: TypeFormatFlags, symbolStack?: Symbol[]): void; + /** @deprecated */ buildParameterDisplay(parameter: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildTypeParameterDisplay(tp: TypeParameter, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildTypePredicateDisplay(predicate: TypePredicate, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildTypeParameterDisplayFromSymbol(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildDisplayForParametersAndDelimiters(thisParameter: Symbol, parameters: Symbol[], writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildDisplayForTypeParametersAndDelimiters(typeParameters: TypeParameter[], writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; + /** @deprecated */ buildReturnTypeDisplay(signature: Signature, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; } - interface SymbolWriter { + /** + * @deprecated Migrate to other methods of generating symbol names, ex symbolToEntityName + a printer or symbolToString + */ + interface SymbolWriter extends SymbolTracker { writeKeyword(text: string): void; writeOperator(text: string): void; writePunctuation(text: string): void; @@ -1827,34 +1882,6 @@ declare namespace ts { increaseIndent(): void; decreaseIndent(): void; clear(): void; - trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): void; - reportInaccessibleThisError(): void; - reportPrivateInBaseOfClassExpression(propertyName: string): void; - reportInaccessibleUniqueSymbolError(): void; - } - enum TypeFormatFlags { - None = 0, - WriteArrayAsGenericType = 1, - UseTypeOfFunction = 4, - NoTruncation = 8, - WriteArrowStyleSignature = 16, - WriteOwnNameForAnyLike = 32, - WriteTypeArgumentsOfSignature = 64, - InElementType = 128, - UseFullyQualifiedType = 256, - InFirstTypeArgument = 512, - InTypeAlias = 1024, - SuppressAnyReturnType = 4096, - AddUndefined = 8192, - WriteClassExpressionAsTypeLiteral = 16384, - InArrayType = 32768, - UseAliasDefinedOutsideCurrentScope = 65536, - AllowUniqueESSymbolType = 131072, - } - enum SymbolFormatFlags { - None = 0, - WriteTypeParametersOrArguments = 1, - UseOnlyExternalAliasing = 2, } enum TypePredicateKind { This = 0, @@ -2634,6 +2661,10 @@ declare namespace ts { * collisions. */ printNode(hint: EmitHint, node: Node, sourceFile: SourceFile): string; + /** + * Prints a list of nodes using the given format flags + */ + printList(format: ListFormat, list: NodeArray, sourceFile: SourceFile): string; /** * Prints a source file as-is, without any emit transformations. */ @@ -2689,6 +2720,13 @@ declare namespace ts { interface PrinterOptions { removeComments?: boolean; newLine?: NewLineKind; + omitTrailingSemicolon?: boolean; + } + interface SymbolTracker { + trackSymbol?(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): void; + reportInaccessibleThisError?(): void; + reportPrivateInBaseOfClassExpression?(propertyName: string): void; + reportInaccessibleUniqueSymbolError?(): void; } interface TextSpan { start: number; @@ -2701,6 +2739,71 @@ declare namespace ts { interface SyntaxList extends Node { _children: Node[]; } + enum ListFormat { + None = 0, + SingleLine = 0, + MultiLine = 1, + PreserveLines = 2, + LinesMask = 3, + NotDelimited = 0, + BarDelimited = 4, + AmpersandDelimited = 8, + CommaDelimited = 16, + DelimitersMask = 28, + AllowTrailingComma = 32, + Indented = 64, + SpaceBetweenBraces = 128, + SpaceBetweenSiblings = 256, + Braces = 512, + Parenthesis = 1024, + AngleBrackets = 2048, + SquareBrackets = 4096, + BracketsMask = 7680, + OptionalIfUndefined = 8192, + OptionalIfEmpty = 16384, + Optional = 24576, + PreferNewLine = 32768, + NoTrailingNewLine = 65536, + NoInterveningComments = 131072, + NoSpaceIfEmpty = 262144, + SingleElement = 524288, + Modifiers = 131328, + HeritageClauses = 256, + SingleLineTypeLiteralMembers = 448, + MultiLineTypeLiteralMembers = 65, + TupleTypeElements = 336, + UnionTypeConstituents = 260, + IntersectionTypeConstituents = 264, + ObjectBindingPatternElements = 262576, + ArrayBindingPatternElements = 262448, + ObjectLiteralExpressionProperties = 263122, + ArrayLiteralExpressionElements = 4466, + CommaListElements = 272, + CallExpressionArguments = 1296, + NewExpressionArguments = 9488, + TemplateExpressionSpans = 131072, + SingleLineBlockStatements = 384, + MultiLineBlockStatements = 65, + VariableDeclarationList = 272, + SingleLineFunctionBodyStatements = 384, + MultiLineFunctionBodyStatements = 1, + ClassHeritageClauses = 256, + ClassMembers = 65, + InterfaceMembers = 65, + EnumMembers = 81, + CaseBlockClauses = 65, + NamedImportsOrExportsElements = 432, + JsxElementOrFragmentChildren = 131072, + JsxElementAttributes = 131328, + CaseOrDefaultClauseStatements = 81985, + HeritageClauseTypes = 272, + SourceFileStatements = 65537, + Decorators = 24577, + TypeArguments = 26896, + TypeParameters = 26896, + Parameters = 1296, + IndexSignatureParameters = 4432, + } } declare namespace ts { const versionMajorMinor = "2.7"; @@ -3243,7 +3346,7 @@ declare namespace ts { function createLiteral(value: string | number | boolean): PrimaryExpression; function createNumericLiteral(value: string): NumericLiteral; function createIdentifier(text: string): Identifier; - function updateIdentifier(node: Identifier, typeArguments: NodeArray | undefined): Identifier; + function updateIdentifier(node: Identifier): Identifier; /** Create a unique temporary variable. */ function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined): Identifier; /** Create a unique temporary variable for use in a loop. */ @@ -4661,9 +4764,6 @@ declare namespace ts { declare namespace ts { /** The version of the language service API */ const servicesVersion = "0.7"; - interface DisplayPartsSymbolWriter extends SymbolWriter { - displayParts(): SymbolDisplayPart[]; - } function toEditorSettings(options: EditorOptions | EditorSettings): EditorSettings; function displayPartsToString(displayParts: SymbolDisplayPart[]): string; function getDefaultCompilerOptions(): CompilerOptions; diff --git a/tests/baselines/reference/commentOnParameter1.js b/tests/baselines/reference/commentOnParameter1.js index 10ae2af1264..3e7ba725017 100644 --- a/tests/baselines/reference/commentOnParameter1.js +++ b/tests/baselines/reference/commentOnParameter1.js @@ -11,10 +11,10 @@ b //// [commentOnParameter1.js] function commentedParameters( - /* Parameter a */ - a - /* End of parameter a */ - /* Parameter b */ - , b - /* End of parameter b */ +/* Parameter a */ +a +/* End of parameter a */ +/* Parameter b */ +, b +/* End of parameter b */ ) { } diff --git a/tests/baselines/reference/commentOnParameter2.js b/tests/baselines/reference/commentOnParameter2.js index d0c024a6b3b..236e660abdf 100644 --- a/tests/baselines/reference/commentOnParameter2.js +++ b/tests/baselines/reference/commentOnParameter2.js @@ -10,9 +10,9 @@ b //// [commentOnParameter2.js] function commentedParameters( - /* Parameter a */ - a /* End of parameter a */ - /* Parameter b */ - , b - /* End of parameter b */ +/* Parameter a */ +a /* End of parameter a */ +/* Parameter b */ +, b +/* End of parameter b */ ) { } diff --git a/tests/baselines/reference/commentsFunction.js b/tests/baselines/reference/commentsFunction.js index ad31aa47b91..5588a27f5ba 100644 --- a/tests/baselines/reference/commentsFunction.js +++ b/tests/baselines/reference/commentsFunction.js @@ -61,8 +61,8 @@ function foo() { foo(); /** This is comment for function signature*/ function fooWithParameters(/** this is comment about a*/ a, - /** this is comment for b*/ - b) { +/** this is comment for b*/ +b) { var d = a; } // trailing comment of function fooWithParameters("a", 10); @@ -78,7 +78,7 @@ var lambddaNoVarComment = function (/**param a*/ a, /**param b*/ b) { return a * lambdaFoo(10, 20); lambddaNoVarComment(10, 20); function blah(a /* multiline trailing comment - multiline */) { +multiline */) { } function blah2(a /* single line multiple trailing comments */ /* second */) { } diff --git a/tests/baselines/reference/declFileConstructors.js b/tests/baselines/reference/declFileConstructors.js index 9f5fc12e002..f40ae2d5382 100644 --- a/tests/baselines/reference/declFileConstructors.js +++ b/tests/baselines/reference/declFileConstructors.js @@ -109,8 +109,8 @@ exports.SimpleConstructor = SimpleConstructor; var ConstructorWithParameters = /** @class */ (function () { /** This is comment for function signature*/ function ConstructorWithParameters(/** this is comment about a*/ a, - /** this is comment for b*/ - b) { + /** this is comment for b*/ + b) { var d = a; } return ConstructorWithParameters; @@ -172,8 +172,8 @@ var GlobalSimpleConstructor = /** @class */ (function () { var GlobalConstructorWithParameters = /** @class */ (function () { /** This is comment for function signature*/ function GlobalConstructorWithParameters(/** this is comment about a*/ a, - /** this is comment for b*/ - b) { + /** this is comment for b*/ + b) { var d = a; } return GlobalConstructorWithParameters; diff --git a/tests/baselines/reference/declFileFunctions.js b/tests/baselines/reference/declFileFunctions.js index 68ac66200a6..3e5c8af32ec 100644 --- a/tests/baselines/reference/declFileFunctions.js +++ b/tests/baselines/reference/declFileFunctions.js @@ -85,8 +85,8 @@ function foo() { exports.foo = foo; /** This is comment for function signature*/ function fooWithParameters(/** this is comment about a*/ a, - /** this is comment for b*/ - b) { +/** this is comment for b*/ +b) { var d = a; } exports.fooWithParameters = fooWithParameters; @@ -131,8 +131,8 @@ function nonExportedFoo() { } /** This is comment for function signature*/ function nonExportedFooWithParameters(/** this is comment about a*/ a, - /** this is comment for b*/ - b) { +/** this is comment for b*/ +b) { var d = a; } function nonExportedFooWithRestParameters(a) { @@ -151,8 +151,8 @@ function globalfoo() { } /** This is comment for function signature*/ function globalfooWithParameters(/** this is comment about a*/ a, - /** this is comment for b*/ - b) { +/** this is comment for b*/ +b) { var d = a; } function globalfooWithRestParameters(a) { diff --git a/tests/baselines/reference/declFileMethods.js b/tests/baselines/reference/declFileMethods.js index 78af9adcd98..a70dde6c3ee 100644 --- a/tests/baselines/reference/declFileMethods.js +++ b/tests/baselines/reference/declFileMethods.js @@ -200,8 +200,8 @@ var c1 = /** @class */ (function () { }; /** This is comment for function signature*/ c1.prototype.fooWithParameters = function (/** this is comment about a*/ a, - /** this is comment for b*/ - b) { + /** this is comment for b*/ + b) { var d = a; }; c1.prototype.fooWithRestParameters = function (a) { @@ -219,8 +219,8 @@ var c1 = /** @class */ (function () { }; /** This is comment for function signature*/ c1.prototype.privateFooWithParameters = function (/** this is comment about a*/ a, - /** this is comment for b*/ - b) { + /** this is comment for b*/ + b) { var d = a; }; c1.prototype.privateFooWithRestParameters = function (a) { @@ -238,8 +238,8 @@ var c1 = /** @class */ (function () { }; /** This is comment for function signature*/ c1.staticFooWithParameters = function (/** this is comment about a*/ a, - /** this is comment for b*/ - b) { + /** this is comment for b*/ + b) { var d = a; }; c1.staticFooWithRestParameters = function (a) { @@ -257,8 +257,8 @@ var c1 = /** @class */ (function () { }; /** This is comment for function signature*/ c1.privateStaticFooWithParameters = function (/** this is comment about a*/ a, - /** this is comment for b*/ - b) { + /** this is comment for b*/ + b) { var d = a; }; c1.privateStaticFooWithRestParameters = function (a) { @@ -283,8 +283,8 @@ var c2 = /** @class */ (function () { }; /** This is comment for function signature*/ c2.prototype.fooWithParameters = function (/** this is comment about a*/ a, - /** this is comment for b*/ - b) { + /** this is comment for b*/ + b) { var d = a; }; c2.prototype.fooWithRestParameters = function (a) { @@ -302,8 +302,8 @@ var c2 = /** @class */ (function () { }; /** This is comment for function signature*/ c2.prototype.privateFooWithParameters = function (/** this is comment about a*/ a, - /** this is comment for b*/ - b) { + /** this is comment for b*/ + b) { var d = a; }; c2.prototype.privateFooWithRestParameters = function (a) { @@ -321,8 +321,8 @@ var c2 = /** @class */ (function () { }; /** This is comment for function signature*/ c2.staticFooWithParameters = function (/** this is comment about a*/ a, - /** this is comment for b*/ - b) { + /** this is comment for b*/ + b) { var d = a; }; c2.staticFooWithRestParameters = function (a) { @@ -340,8 +340,8 @@ var c2 = /** @class */ (function () { }; /** This is comment for function signature*/ c2.privateStaticFooWithParameters = function (/** this is comment about a*/ a, - /** this is comment for b*/ - b) { + /** this is comment for b*/ + b) { var d = a; }; c2.privateStaticFooWithRestParameters = function (a) { diff --git a/tests/baselines/reference/declarationEmitBindingPatterns.js b/tests/baselines/reference/declarationEmitBindingPatterns.js index 800e30032c6..70ac7f437d3 100644 --- a/tests/baselines/reference/declarationEmitBindingPatterns.js +++ b/tests/baselines/reference/declarationEmitBindingPatterns.js @@ -18,7 +18,7 @@ function f(_a, _b, _c) { //// [declarationEmitBindingPatterns.d.ts] -declare const k: ({x: z}: { +declare const k: ({ x: z }: { x?: string; }) => void; declare var a: any; diff --git a/tests/baselines/reference/declarationEmitDestructuring2.js b/tests/baselines/reference/declarationEmitDestructuring2.js index 09980c5c9dd..c979b654960 100644 --- a/tests/baselines/reference/declarationEmitDestructuring2.js +++ b/tests/baselines/reference/declarationEmitDestructuring2.js @@ -26,18 +26,18 @@ declare function f({x, y: [a, b, c, d]}?: { }): void; declare function g([a, b, c, d]?: [number, number, number, number]): void; declare function h([a, [b], [[c]], {x, y: [a, b, c], z: {a1, b1}}]: [any, [any], [[any]], { - x?: number; - y: [any, any, any]; - z: { - a1: any; - b1: any; - }; -}]): void; + x?: number; + y: [any, any, any]; + z: { + a1: any; + b1: any; + }; + }]): void; declare function h1([a, [b], [[c]], {x, y, z: {a1, b1}}]: [any, [any], [[any]], { - x?: number; - y?: number[]; - z: { - a1: any; - b1: any; - }; -}]): void; + x?: number; + y?: number[]; + z: { + a1: any; + b1: any; + }; + }]): void; diff --git a/tests/baselines/reference/declarationEmitIndexTypeArray.js b/tests/baselines/reference/declarationEmitIndexTypeArray.js index a84080fb5c4..9fe8878a8db 100644 --- a/tests/baselines/reference/declarationEmitIndexTypeArray.js +++ b/tests/baselines/reference/declarationEmitIndexTypeArray.js @@ -21,5 +21,5 @@ var utilityFunctions = { //// [declarationEmitIndexTypeArray.d.ts] declare function doSomethingWithKeys(...keys: (keyof T)[]): void; declare const utilityFunctions: { - doSomethingWithKeys: (...keys: (keyof T)[]) => void; + doSomethingWithKeys: typeof doSomethingWithKeys; }; diff --git a/tests/baselines/reference/declarationEmitTypeofDefaultExport.symbols b/tests/baselines/reference/declarationEmitTypeofDefaultExport.symbols index 195f10a78a1..898a9883781 100644 --- a/tests/baselines/reference/declarationEmitTypeofDefaultExport.symbols +++ b/tests/baselines/reference/declarationEmitTypeofDefaultExport.symbols @@ -7,7 +7,7 @@ import * as a from "./a"; >a : Symbol(a, Decl(b.ts, 0, 6)) export default a.default; ->a.default : Symbol(a.default, Decl(a.ts, 0, 0)) +>a.default : Symbol(a.C, Decl(a.ts, 0, 0)) >a : Symbol(a, Decl(b.ts, 0, 6)) ->default : Symbol(a.default, Decl(a.ts, 0, 0)) +>default : Symbol(a.C, Decl(a.ts, 0, 0)) diff --git a/tests/baselines/reference/deferredLookupTypeResolution.js b/tests/baselines/reference/deferredLookupTypeResolution.js index 5f8edd63e57..e5baa6891e8 100644 --- a/tests/baselines/reference/deferredLookupTypeResolution.js +++ b/tests/baselines/reference/deferredLookupTypeResolution.js @@ -54,9 +54,7 @@ declare type T2 = ObjectHasKey<{ declare function f1(a: A, b: B): { [P in A | B]: any; }; -declare function f2(a: A): { - [P in A | "x"]: any; -}; +declare function f2(a: A): { [P in A | "x"]: any; }; declare function f3(x: 'a' | 'b'): { a: any; b: any; diff --git a/tests/baselines/reference/deferredLookupTypeResolution.types b/tests/baselines/reference/deferredLookupTypeResolution.types index d9486d30b07..cd123a6019e 100644 --- a/tests/baselines/reference/deferredLookupTypeResolution.types +++ b/tests/baselines/reference/deferredLookupTypeResolution.types @@ -17,7 +17,7 @@ type StringContains = ( >L : L type ObjectHasKey = StringContains ->ObjectHasKey : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>ObjectHasKey : ({ [K in keyof O]: "true"; } & { [key: string]: "false"; })[L] >O : O >L : L >StringContains : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] @@ -25,19 +25,19 @@ type ObjectHasKey = StringContains >L : L type First = ObjectHasKey; // Should be deferred ->First : ({ [K in S]: "true"; } & { [key: string]: "false"; })["0"] +>First : ({ [K in keyof T]: "true"; } & { [key: string]: "false"; })["0"] >T : T ->ObjectHasKey : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>ObjectHasKey : ({ [K in keyof O]: "true"; } & { [key: string]: "false"; })[L] >T : T type T1 = ObjectHasKey<{ a: string }, 'a'>; // 'true' >T1 : "true" ->ObjectHasKey : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>ObjectHasKey : ({ [K in keyof O]: "true"; } & { [key: string]: "false"; })[L] >a : string type T2 = ObjectHasKey<{ a: string }, 'b'>; // 'false' >T2 : "false" ->ObjectHasKey : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>ObjectHasKey : ({ [K in keyof O]: "true"; } & { [key: string]: "false"; })[L] >a : string // Verify that mapped type isn't eagerly resolved in type-to-string operation @@ -55,13 +55,13 @@ declare function f1(a: A, b: B): { [P in A | >B : B function f2(a: A) { ->f2 : (a: A) => { [P in A | B]: any; } +>f2 : (a: A) => { [P in A | "x"]: any; } >A : A >a : A >A : A return f1(a, 'x'); ->f1(a, 'x') : { [P in A | B]: any; } +>f1(a, 'x') : { [P in A | "x"]: any; } >f1 : (a: A, b: B) => { [P in A | B]: any; } >a : A >'x' : "x" @@ -73,7 +73,7 @@ function f3(x: 'a' | 'b') { return f2(x); >f2(x) : { a: any; b: any; x: any; } ->f2 : (a: A) => { [P in A | B]: any; } +>f2 : (a: A) => { [P in A | "x"]: any; } >x : "a" | "b" } diff --git a/tests/baselines/reference/deferredLookupTypeResolution2.errors.txt b/tests/baselines/reference/deferredLookupTypeResolution2.errors.txt index f6bbe72f1a6..6d1d579c0ad 100644 --- a/tests/baselines/reference/deferredLookupTypeResolution2.errors.txt +++ b/tests/baselines/reference/deferredLookupTypeResolution2.errors.txt @@ -1,5 +1,5 @@ -tests/cases/compiler/deferredLookupTypeResolution2.ts(14,13): error TS2536: Type '({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]' cannot be used to index type '{ true: "true"; }'. -tests/cases/compiler/deferredLookupTypeResolution2.ts(19,21): error TS2536: Type '({ true: "otherwise"; } & { [k: string]: "true"; })[({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]]' cannot be used to index type '{ true: "true"; }'. +tests/cases/compiler/deferredLookupTypeResolution2.ts(14,13): error TS2536: Type '({ [K in keyof T]: "true"; } & { [key: string]: "false"; })["1"]' cannot be used to index type '{ true: "true"; }'. +tests/cases/compiler/deferredLookupTypeResolution2.ts(19,21): error TS2536: Type '({ true: "otherwise"; } & { [k: string]: "true"; })[({ [K in keyof T]: "true"; } & { [key: string]: "false"; })["1"]]' cannot be used to index type '{ true: "true"; }'. ==== tests/cases/compiler/deferredLookupTypeResolution2.ts (2 errors) ==== @@ -18,14 +18,14 @@ tests/cases/compiler/deferredLookupTypeResolution2.ts(19,21): error TS2536: Type // Error, "false" not handled type E = { true: 'true' }[ObjectHasKey]; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2536: Type '({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]' cannot be used to index type '{ true: "true"; }'. +!!! error TS2536: Type '({ [K in keyof T]: "true"; } & { [key: string]: "false"; })["1"]' cannot be used to index type '{ true: "true"; }'. type Juxtapose = ({ true: 'otherwise' } & { [k: string]: 'true' })[ObjectHasKey]; // Error, "otherwise" is missing type DeepError = { true: 'true' }[Juxtapose]; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2536: Type '({ true: "otherwise"; } & { [k: string]: "true"; })[({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]]' cannot be used to index type '{ true: "true"; }'. +!!! error TS2536: Type '({ true: "otherwise"; } & { [k: string]: "true"; })[({ [K in keyof T]: "true"; } & { [key: string]: "false"; })["1"]]' cannot be used to index type '{ true: "true"; }'. type DeepOK = { true: 'true', otherwise: 'false' }[Juxtapose]; \ No newline at end of file diff --git a/tests/baselines/reference/deferredLookupTypeResolution2.types b/tests/baselines/reference/deferredLookupTypeResolution2.types index 25b3ceff934..76354731097 100644 --- a/tests/baselines/reference/deferredLookupTypeResolution2.types +++ b/tests/baselines/reference/deferredLookupTypeResolution2.types @@ -11,7 +11,7 @@ type StringContains = ({ [K in S]: 'true' } >L : L type ObjectHasKey = StringContains; ->ObjectHasKey : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>ObjectHasKey : ({ [K in keyof O]: "true"; } & { [key: string]: "false"; })[L] >O : O >L : L >StringContains : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] @@ -19,52 +19,52 @@ type ObjectHasKey = StringContains; >L : L type A = ObjectHasKey; ->A : ({ [K in S]: "true"; } & { [key: string]: "false"; })["0"] +>A : ({ [K in keyof T]: "true"; } & { [key: string]: "false"; })["0"] >T : T ->ObjectHasKey : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>ObjectHasKey : ({ [K in keyof O]: "true"; } & { [key: string]: "false"; })[L] >T : T type B = ObjectHasKey<[string, number], '1'>; // "true" >B : "true" ->ObjectHasKey : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>ObjectHasKey : ({ [K in keyof O]: "true"; } & { [key: string]: "false"; })[L] type C = ObjectHasKey<[string, number], '2'>; // "false" >C : "false" ->ObjectHasKey : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>ObjectHasKey : ({ [K in keyof O]: "true"; } & { [key: string]: "false"; })[L] type D = A<[string]>; // "true" >D : "true" ->A : ({ [K in S]: "true"; } & { [key: string]: "false"; })["0"] +>A : ({ [K in keyof T]: "true"; } & { [key: string]: "false"; })["0"] // Error, "false" not handled type E = { true: 'true' }[ObjectHasKey]; ->E : { true: "true"; }[({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]] +>E : { true: "true"; }[({ [K in keyof T]: "true"; } & { [key: string]: "false"; })["1"]] >T : T >true : "true" ->ObjectHasKey : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>ObjectHasKey : ({ [K in keyof O]: "true"; } & { [key: string]: "false"; })[L] >T : T type Juxtapose = ({ true: 'otherwise' } & { [k: string]: 'true' })[ObjectHasKey]; ->Juxtapose : ({ true: "otherwise"; } & { [k: string]: "true"; })[({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]] +>Juxtapose : ({ true: "otherwise"; } & { [k: string]: "true"; })[({ [K in keyof T]: "true"; } & { [key: string]: "false"; })["1"]] >T : T >true : "otherwise" >k : string ->ObjectHasKey : ({ [K in S]: "true"; } & { [key: string]: "false"; })[L] +>ObjectHasKey : ({ [K in keyof O]: "true"; } & { [key: string]: "false"; })[L] >T : T // Error, "otherwise" is missing type DeepError = { true: 'true' }[Juxtapose]; ->DeepError : { true: "true"; }[({ true: "otherwise"; } & { [k: string]: "true"; })[({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]]] +>DeepError : { true: "true"; }[({ true: "otherwise"; } & { [k: string]: "true"; })[({ [K in keyof T]: "true"; } & { [key: string]: "false"; })["1"]]] >T : T >true : "true" ->Juxtapose : ({ true: "otherwise"; } & { [k: string]: "true"; })[({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]] +>Juxtapose : ({ true: "otherwise"; } & { [k: string]: "true"; })[({ [K in keyof T]: "true"; } & { [key: string]: "false"; })["1"]] >T : T type DeepOK = { true: 'true', otherwise: 'false' }[Juxtapose]; ->DeepOK : { true: "true"; otherwise: "false"; }[({ true: "otherwise"; } & { [k: string]: "true"; })[({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]]] +>DeepOK : { true: "true"; otherwise: "false"; }[({ true: "otherwise"; } & { [k: string]: "true"; })[({ [K in keyof T]: "true"; } & { [key: string]: "false"; })["1"]]] >T : T >true : "true" >otherwise : "false" ->Juxtapose : ({ true: "otherwise"; } & { [k: string]: "true"; })[({ [K in S]: "true"; } & { [key: string]: "false"; })["1"]] +>Juxtapose : ({ true: "otherwise"; } & { [k: string]: "true"; })[({ [K in keyof T]: "true"; } & { [key: string]: "false"; })["1"]] >T : T diff --git a/tests/baselines/reference/isomorphicMappedTypeInference.js b/tests/baselines/reference/isomorphicMappedTypeInference.js index aa000bc2913..e7df29894f9 100644 --- a/tests/baselines/reference/isomorphicMappedTypeInference.js +++ b/tests/baselines/reference/isomorphicMappedTypeInference.js @@ -275,9 +275,7 @@ declare function f3(): void; declare function f4(): void; declare function makeRecord(obj: { [P in K]: T; -}): { - [P in K]: T; -}; +}): { [P in K]: T; }; declare function f5(s: string): void; declare function makeDictionary(obj: { [x: string]: T; diff --git a/tests/baselines/reference/objectTypeWithStringNamedPropertyOfIllegalCharacters.symbols b/tests/baselines/reference/objectTypeWithStringNamedPropertyOfIllegalCharacters.symbols index 773fb3f5760..558ec029790 100644 --- a/tests/baselines/reference/objectTypeWithStringNamedPropertyOfIllegalCharacters.symbols +++ b/tests/baselines/reference/objectTypeWithStringNamedPropertyOfIllegalCharacters.symbols @@ -31,7 +31,7 @@ var r3 = c["a b"]; var r4 = c["~!@#$%^&*()_+{}|:'<>?\/.,`"]; >r4 : Symbol(r4, Decl(objectTypeWithStringNamedPropertyOfIllegalCharacters.ts, 13, 3), Decl(objectTypeWithStringNamedPropertyOfIllegalCharacters.ts, 26, 3), Decl(objectTypeWithStringNamedPropertyOfIllegalCharacters.ts, 39, 3), Decl(objectTypeWithStringNamedPropertyOfIllegalCharacters.ts, 51, 3)) >c : Symbol(c, Decl(objectTypeWithStringNamedPropertyOfIllegalCharacters.ts, 8, 3)) ->"~!@#$%^&*()_+{}|:'<>?\/.,`" : Symbol(C["~!@#$%^&*()_+{}|:'<>?\/.,`"], Decl(objectTypeWithStringNamedPropertyOfIllegalCharacters.ts, 2, 20)) +>"~!@#$%^&*()_+{}|:'<>?\/.,`" : Symbol(C["~!@#$%^&*()_+{}|:'<>?/.,`"], Decl(objectTypeWithStringNamedPropertyOfIllegalCharacters.ts, 2, 20)) interface I { >I : Symbol(I, Decl(objectTypeWithStringNamedPropertyOfIllegalCharacters.ts, 13, 41)) @@ -63,7 +63,7 @@ var r3 = i["a b"]; var r4 = i["~!@#$%^&*()_+{}|:'<>?\/.,`"]; >r4 : Symbol(r4, Decl(objectTypeWithStringNamedPropertyOfIllegalCharacters.ts, 13, 3), Decl(objectTypeWithStringNamedPropertyOfIllegalCharacters.ts, 26, 3), Decl(objectTypeWithStringNamedPropertyOfIllegalCharacters.ts, 39, 3), Decl(objectTypeWithStringNamedPropertyOfIllegalCharacters.ts, 51, 3)) >i : Symbol(i, Decl(objectTypeWithStringNamedPropertyOfIllegalCharacters.ts, 21, 3)) ->"~!@#$%^&*()_+{}|:'<>?\/.,`" : Symbol(I["~!@#$%^&*()_+{}|:'<>?\/.,`"], Decl(objectTypeWithStringNamedPropertyOfIllegalCharacters.ts, 17, 20)) +>"~!@#$%^&*()_+{}|:'<>?\/.,`" : Symbol(I["~!@#$%^&*()_+{}|:'<>?/.,`"], Decl(objectTypeWithStringNamedPropertyOfIllegalCharacters.ts, 17, 20)) var a: { diff --git a/tests/baselines/reference/quickInfoDisplayPartsLiteralLikeNames01.baseline b/tests/baselines/reference/quickInfoDisplayPartsLiteralLikeNames01.baseline index 39ab90e8e09..1d2d8216808 100644 --- a/tests/baselines/reference/quickInfoDisplayPartsLiteralLikeNames01.baseline +++ b/tests/baselines/reference/quickInfoDisplayPartsLiteralLikeNames01.baseline @@ -38,7 +38,7 @@ }, { "text": "1", - "kind": "methodName" + "kind": "stringLiteral" }, { "text": "]", @@ -310,7 +310,7 @@ }, { "text": "1", - "kind": "methodName" + "kind": "stringLiteral" }, { "text": "]", @@ -380,7 +380,7 @@ }, { "text": "1", - "kind": "methodName" + "kind": "stringLiteral" }, { "text": "]", diff --git a/tests/baselines/reference/recursiveTypeRelations.types b/tests/baselines/reference/recursiveTypeRelations.types index 110ff8175c4..03008690d8d 100644 --- a/tests/baselines/reference/recursiveTypeRelations.types +++ b/tests/baselines/reference/recursiveTypeRelations.types @@ -17,7 +17,7 @@ class Query> { >A : A multiply>(x: B): Query; ->multiply : (x: B) => Query +>multiply : (x: B) => Query >B : B >Attributes : { [Key in Keys]: string; } >B : B diff --git a/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.js b/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.js index 84db5d4a4a9..5f8a850c589 100644 --- a/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.js +++ b/tests/baselines/reference/typeGuardFunctionOfFormThisErrors.js @@ -152,6 +152,6 @@ declare let b: GuardInterface; declare function invalidGuard(c: any): this is number; declare let c: number | number[]; declare let holder: { - invalidGuard: (c: any) => this is number; + invalidGuard: typeof invalidGuard; }; declare let detached: () => this is FollowerGuard; diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMappedType.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMappedType.ts index b787d0c271e..5fff622f2db 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMappedType.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMappedType.ts @@ -13,6 +13,6 @@ verify.codeFix({ x: { readonly [K in keyof X]: X[K] }; } class C implements I {\r - x: { readonly [K in keyof X]: Y[K]; };\r + x: { readonly [K in keyof Y]: Y[K]; };\r }`, }); From 639b2781d7075492b9e6b5ef92e159f8d6cbc48f Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 16 Jan 2018 11:13:53 -0800 Subject: [PATCH 087/172] Accept corrected baseline (#21201) --- .../reference/indexedAccessRetainsIndexSignature.types | 6 +++--- .../user/TypeScript-Node-Starter/TypeScript-Node-Starter | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/baselines/reference/indexedAccessRetainsIndexSignature.types b/tests/baselines/reference/indexedAccessRetainsIndexSignature.types index 3e3f52f7ca6..8dcbe9bf218 100644 --- a/tests/baselines/reference/indexedAccessRetainsIndexSignature.types +++ b/tests/baselines/reference/indexedAccessRetainsIndexSignature.types @@ -14,7 +14,7 @@ type Diff = >T : T type Omit = Pick> ->Omit : Pick +>Omit : Pick >U : U >K : K >U : U @@ -25,7 +25,7 @@ type Omit = Pick> >K : K type Omit1 = Pick>; ->Omit1 : Pick +>Omit1 : Pick >U : U >K : K >U : U @@ -51,7 +51,7 @@ type Omit2 = {[P in Diff]: T[P]}; type O = Omit<{ a: number, b: string }, 'a'> >O : Pick<{ a: number; b: string; }, "b"> ->Omit : Pick +>Omit : Pick >a : number >b : string diff --git a/tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter b/tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter index ed149eb0c78..40bdb4eadab 160000 --- a/tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter +++ b/tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter @@ -1 +1 @@ -Subproject commit ed149eb0c787b1195a95b44105822c64bb6eb636 +Subproject commit 40bdb4eadabc9fbed7d83e3f26817a931c0763b6 From 68aad1b85e57f48b34ab2add6d5424dc17fa355a Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Tue, 16 Jan 2018 09:50:14 -0800 Subject: [PATCH 088/172] Normalize triple slash reference paths at resolve time --- src/compiler/program.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 0d1bbf233a3..8f3cadcddc5 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -15,7 +15,8 @@ namespace ts { export function resolveTripleslashReference(moduleName: string, containingFile: string): string { const basePath = getDirectoryPath(containingFile); - const referencedFileName = isRootedDiskPath(moduleName) ? moduleName : combinePaths(basePath, moduleName); + const normalizedModuleName = normalizePath(moduleName); + const referencedFileName = isRootedDiskPath(normalizedModuleName) ? normalizedModuleName : combinePaths(basePath, normalizedModuleName); return normalizePath(referencedFileName); } From 5ea43db6ecee7db00586e4438131200e18cea2b8 Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Tue, 16 Jan 2018 11:25:54 -0800 Subject: [PATCH 089/172] Add test --- .../tripleSlashReferenceAbsoluteWindowsPath.js | 14 ++++++++++++++ ...tripleSlashReferenceAbsoluteWindowsPath.symbols | 10 ++++++++++ .../tripleSlashReferenceAbsoluteWindowsPath.types | 13 +++++++++++++ .../tripleSlashReferenceAbsoluteWindowsPath.ts | 6 ++++++ 4 files changed, 43 insertions(+) create mode 100644 tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.js create mode 100644 tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.symbols create mode 100644 tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.types create mode 100644 tests/cases/compiler/tripleSlashReferenceAbsoluteWindowsPath.ts diff --git a/tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.js b/tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.js new file mode 100644 index 00000000000..3e719abde5f --- /dev/null +++ b/tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.js @@ -0,0 +1,14 @@ +//// [tests/cases/compiler/tripleSlashReferenceAbsoluteWindowsPath.ts] //// + +//// [c.ts] +const x = 5; + +//// [d.ts] +/// +const y = x + 3; + +//// [c.js] +var x = 5; +//// [d.js] +/// +var y = x + 3; diff --git a/tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.symbols b/tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.symbols new file mode 100644 index 00000000000..0a3b6aedc6b --- /dev/null +++ b/tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.symbols @@ -0,0 +1,10 @@ +=== C:/a/b/d.ts === +/// +const y = x + 3; +>y : Symbol(y, Decl(d.ts, 1, 5)) +>x : Symbol(x, Decl(c.ts, 0, 5)) + +=== C:/a/b/c.ts === +const x = 5; +>x : Symbol(x, Decl(c.ts, 0, 5)) + diff --git a/tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.types b/tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.types new file mode 100644 index 00000000000..fdc37ae2faf --- /dev/null +++ b/tests/baselines/reference/tripleSlashReferenceAbsoluteWindowsPath.types @@ -0,0 +1,13 @@ +=== C:/a/b/d.ts === +/// +const y = x + 3; +>y : number +>x + 3 : number +>x : 5 +>3 : 3 + +=== C:/a/b/c.ts === +const x = 5; +>x : 5 +>5 : 5 + diff --git a/tests/cases/compiler/tripleSlashReferenceAbsoluteWindowsPath.ts b/tests/cases/compiler/tripleSlashReferenceAbsoluteWindowsPath.ts new file mode 100644 index 00000000000..b45e2a52b65 --- /dev/null +++ b/tests/cases/compiler/tripleSlashReferenceAbsoluteWindowsPath.ts @@ -0,0 +1,6 @@ +//@Filename: C:\a\b\c.ts +const x = 5; + +//@Filename: C:\a\b\d.ts +/// +const y = x + 3; \ No newline at end of file From 7c7651d617efe89066a4efeb6cbb13ab766e03c5 Mon Sep 17 00:00:00 2001 From: Klaus Meinhardt Date: Tue, 16 Jan 2018 21:11:06 +0100 Subject: [PATCH 090/172] Add overloads for forEach{Leading,Trailing}CommentRange (#21190) Avoids runtime errors when passing callback with state parameter but not passing a state. --- src/compiler/scanner.ts | 4 ++++ tests/baselines/reference/api/tsserverlibrary.d.ts | 6 ++++-- tests/baselines/reference/api/typescript.d.ts | 6 ++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index c8bb2ec0a25..5001ea58336 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -725,10 +725,14 @@ namespace ts { return accumulator; } + export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean) => U): U | undefined; + export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state: T): U | undefined; export function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined { return iterateCommentRanges(/*reduce*/ false, text, pos, /*trailing*/ false, cb, state); } + export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean) => U): U | undefined; + export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state: T): U | undefined; export function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined { return iterateCommentRanges(/*reduce*/ false, text, pos, /*trailing*/ true, cb, state); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index bee3d4d2637..0a1385e9553 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3254,8 +3254,10 @@ declare namespace ts { function isWhiteSpaceSingleLine(ch: number): boolean; function isLineBreak(ch: number): boolean; function couldStartTrivia(text: string, pos: number): boolean; - function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined; - function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined; + function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean) => U): U | undefined; + function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state: T): U | undefined; + function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean) => U): U | undefined; + function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state: T): U | undefined; function reduceEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U): U; function reduceEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U): U; function getLeadingCommentRanges(text: string, pos: number): CommentRange[] | undefined; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 09db501300b..82995f97bfa 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2915,8 +2915,10 @@ declare namespace ts { function isWhiteSpaceSingleLine(ch: number): boolean; function isLineBreak(ch: number): boolean; function couldStartTrivia(text: string, pos: number): boolean; - function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined; - function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state?: T): U | undefined; + function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean) => U): U | undefined; + function forEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state: T): U | undefined; + function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean) => U): U | undefined; + function forEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T) => U, state: T): U | undefined; function reduceEachLeadingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U): U; function reduceEachTrailingCommentRange(text: string, pos: number, cb: (pos: number, end: number, kind: CommentKind, hasTrailingNewLine: boolean, state: T, memo: U) => U, state: T, initial: U): U; function getLeadingCommentRanges(text: string, pos: number): CommentRange[] | undefined; From 1785d87fdae732aaa02652d6f20d4b94d0278a56 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 16 Jan 2018 12:33:55 -0800 Subject: [PATCH 091/172] Fix temp variable emit for names used in nested classes --- src/compiler/emitter.ts | 42 +++++++++++---- src/compiler/factory.ts | 24 +++++---- src/compiler/transformers/ts.ts | 9 ++-- src/compiler/types.ts | 26 +++++----- src/compiler/utilities.ts | 2 +- .../reference/asyncAwaitNestedClasses_es5.js | 52 +++++++++++++++++++ .../asyncAwaitNestedClasses_es5.symbols | 43 +++++++++++++++ .../asyncAwaitNestedClasses_es5.types | 52 +++++++++++++++++++ .../async/es5/asyncAwaitNestedClasses_es5.ts | 18 +++++++ 9 files changed, 232 insertions(+), 36 deletions(-) create mode 100644 tests/baselines/reference/asyncAwaitNestedClasses_es5.js create mode 100644 tests/baselines/reference/asyncAwaitNestedClasses_es5.symbols create mode 100644 tests/baselines/reference/asyncAwaitNestedClasses_es5.types create mode 100644 tests/cases/conformance/async/es5/asyncAwaitNestedClasses_es5.ts diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 8a1de17826c..705c0811cdc 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -289,6 +289,9 @@ namespace ts { let generatedNames: Map; // Set of names generated by the NameGenerator. let tempFlagsStack: TempFlags[]; // Stack of enclosing name generation scopes. let tempFlags: TempFlags; // TempFlags for the current name generation scope. + let reservedNamesStack: Map[]; // Stack of TempFlags reserved in enclosing name generation scopes. + let reservedNames: Map; // TempFlags to reserve in nested name generation scopes. + let writer: EmitTextWriter; let ownWriter: EmitTextWriter; let write = writeBase; @@ -434,6 +437,7 @@ namespace ts { generatedNames = createMap(); tempFlagsStack = []; tempFlags = TempFlags.Auto; + reservedNamesStack = []; comments.reset(); setWriter(/*output*/ undefined); } @@ -3083,6 +3087,7 @@ namespace ts { } tempFlagsStack.push(tempFlags); tempFlags = 0; + reservedNamesStack.push(reservedNames); } /** @@ -3093,16 +3098,24 @@ namespace ts { return; } tempFlags = tempFlagsStack.pop(); + reservedNames = reservedNamesStack.pop(); + } + + function reserveNameInNestedScopes(name: string) { + if (!reservedNames || reservedNames === lastOrUndefined(reservedNamesStack)) { + reservedNames = createMap(); + } + reservedNames.set(name, true); } /** * Generate the text for a generated identifier. */ function generateName(name: GeneratedIdentifier) { - if (name.autoGenerateKind === GeneratedIdentifierKind.Node) { + if ((name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) === GeneratedIdentifierFlags.Node) { // Node names generate unique names based on their original node // and are cached based on that node's id. - if (name.skipNameGenerationScope) { + if (name.autoGenerateFlags & GeneratedIdentifierFlags.SkipNameGenerationScope) { const savedTempFlags = tempFlags; popNameGenerationScope(/*node*/ undefined); const result = generateNameCached(getNodeForGeneratedName(name)); @@ -3134,7 +3147,8 @@ namespace ts { function isUniqueName(name: string): boolean { return !(hasGlobalName && hasGlobalName(name)) && !currentSourceFile.identifiers.has(name) - && !generatedNames.has(name); + && !generatedNames.has(name) + && !(reservedNames && reservedNames.has(name)); } /** @@ -3158,11 +3172,14 @@ namespace ts { * TempFlags._i or TempFlags._n may be used to express a preference for that dedicated name. * Note that names generated by makeTempVariableName and makeUniqueName will never conflict. */ - function makeTempVariableName(flags: TempFlags): string { + function makeTempVariableName(flags: TempFlags, reservedInNestedScopes?: boolean): string { if (flags && !(tempFlags & flags)) { const name = flags === TempFlags._i ? "_i" : "_n"; if (isUniqueName(name)) { tempFlags |= flags; + if (reservedInNestedScopes) { + reserveNameInNestedScopes(name); + } return name; } } @@ -3175,6 +3192,9 @@ namespace ts { ? "_" + String.fromCharCode(CharacterCodes.a + count) : "_" + (count - 26); if (isUniqueName(name)) { + if (reservedInNestedScopes) { + reserveNameInNestedScopes(name); + } return name; } } @@ -3275,12 +3295,12 @@ namespace ts { * Generates a unique identifier for a node. */ function makeName(name: GeneratedIdentifier) { - switch (name.autoGenerateKind) { - case GeneratedIdentifierKind.Auto: - return makeTempVariableName(TempFlags.Auto); - case GeneratedIdentifierKind.Loop: - return makeTempVariableName(TempFlags._i); - case GeneratedIdentifierKind.Unique: + switch (name.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) { + case GeneratedIdentifierFlags.Auto: + return makeTempVariableName(TempFlags.Auto, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)); + case GeneratedIdentifierFlags.Loop: + return makeTempVariableName(TempFlags._i, !!(name.autoGenerateFlags & GeneratedIdentifierFlags.ReservedInNestedScopes)); + case GeneratedIdentifierFlags.Unique: return makeUniqueName(idText(name)); } @@ -3300,7 +3320,7 @@ namespace ts { // if "node" is a different generated name (having a different // "autoGenerateId"), use it and stop traversing. if (isIdentifier(node) - && node.autoGenerateKind === GeneratedIdentifierKind.Node + && node.autoGenerateFlags === GeneratedIdentifierFlags.Node && node.autoGenerateId !== autoGenerateId) { break; } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 14c8ad91eeb..0a006e1c73a 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -117,7 +117,7 @@ namespace ts { const node = createSynthesizedNode(SyntaxKind.Identifier); node.escapedText = escapeLeadingUnderscores(text); node.originalKeywordKind = text ? stringToToken(text) : SyntaxKind.Unknown; - node.autoGenerateKind = GeneratedIdentifierKind.None; + node.autoGenerateFlags = GeneratedIdentifierFlags.None; node.autoGenerateId = 0; if (typeArguments) { node.typeArguments = createNodeArray(typeArguments as ReadonlyArray); @@ -137,21 +137,26 @@ namespace ts { let nextAutoGenerateId = 0; /** Create a unique temporary variable. */ - export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined): Identifier { + export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined): Identifier; + /* @internal */ export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, reservedInNestedScopes: boolean): Identifier; // tslint:disable-line unified-signatures + export function createTempVariable(recordTempVariable: ((node: Identifier) => void) | undefined, reservedInNestedScopes?: boolean): Identifier { const name = createIdentifier(""); - name.autoGenerateKind = GeneratedIdentifierKind.Auto; + name.autoGenerateFlags = GeneratedIdentifierFlags.Auto; name.autoGenerateId = nextAutoGenerateId; nextAutoGenerateId++; if (recordTempVariable) { recordTempVariable(name); } + if (reservedInNestedScopes) { + name.autoGenerateFlags |= GeneratedIdentifierFlags.ReservedInNestedScopes; + } return name; } /** Create a unique temporary variable for use in a loop. */ export function createLoopVariable(): Identifier { const name = createIdentifier(""); - name.autoGenerateKind = GeneratedIdentifierKind.Loop; + name.autoGenerateFlags = GeneratedIdentifierFlags.Loop; name.autoGenerateId = nextAutoGenerateId; nextAutoGenerateId++; return name; @@ -160,7 +165,7 @@ namespace ts { /** Create a unique name based on the supplied text. */ export function createUniqueName(text: string): Identifier { const name = createIdentifier(text); - name.autoGenerateKind = GeneratedIdentifierKind.Unique; + name.autoGenerateFlags = GeneratedIdentifierFlags.Unique; name.autoGenerateId = nextAutoGenerateId; nextAutoGenerateId++; return name; @@ -168,14 +173,15 @@ namespace ts { /** Create a unique name generated for a node. */ export function getGeneratedNameForNode(node: Node): Identifier; - // tslint:disable-next-line unified-signatures - /*@internal*/ export function getGeneratedNameForNode(node: Node, shouldSkipNameGenerationScope?: boolean): Identifier; + /* @internal */ export function getGeneratedNameForNode(node: Node, shouldSkipNameGenerationScope?: boolean): Identifier; // tslint:disable-line unified-signatures export function getGeneratedNameForNode(node: Node, shouldSkipNameGenerationScope?: boolean): Identifier { const name = createIdentifier(""); - name.autoGenerateKind = GeneratedIdentifierKind.Node; + name.autoGenerateFlags = GeneratedIdentifierFlags.Node; name.autoGenerateId = nextAutoGenerateId; name.original = node; - name.skipNameGenerationScope = !!shouldSkipNameGenerationScope; + if (shouldSkipNameGenerationScope) { + name.autoGenerateFlags |= GeneratedIdentifierFlags.SkipNameGenerationScope; + } nextAutoGenerateId++; return name; } diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index cdb0d0667ad..f6c5d145a90 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -893,11 +893,14 @@ namespace ts { if (some(staticProperties) || some(pendingExpressions)) { const expressions: Expression[] = []; - const temp = createTempVariable(hoistVariableDeclaration); - if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference) { + const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference; + const temp = createTempVariable(hoistVariableDeclaration, !!isClassWithConstructorReference); + if (isClassWithConstructorReference) { // record an alias as the class name is not in scope for statics. enableSubstitutionForClassAliases(); - classAliases[getOriginalNodeId(node)] = getSynthesizedClone(temp); + const alias = getSynthesizedClone(temp); + alias.autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes; + classAliases[getOriginalNodeId(node)] = alias; } // To preserve the behavior of the old emitter, we explicitly indent diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6622348e101..478585adb01 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -676,12 +676,18 @@ namespace ts { export type ModifiersArray = NodeArray; /*@internal*/ - export const enum GeneratedIdentifierKind { - None, // Not automatically generated. - Auto, // Automatically generated identifier. - Loop, // Automatically generated identifier with a preference for '_i'. - Unique, // Unique name based on the 'text' property. - Node, // Unique name based on the node in the 'original' property. + export const enum GeneratedIdentifierFlags { + // Kinds + None = 0, // Not automatically generated. + Auto = 1, // Automatically generated identifier. + Loop = 2, // Automatically generated identifier with a preference for '_i'. + Unique = 3, // Unique name based on the 'text' property. + Node = 4, // Unique name based on the node in the 'original' property. + KindMask = 7, // Mask to extract the kind of identifier from its flags. + + // Flags + SkipNameGenerationScope = 1 << 3, // Should skip a name generation scope when generating the name for this identifier + ReservedInNestedScopes = 1 << 4, // Reserve the generated name in nested scopes } export interface Identifier extends PrimaryExpression, Declaration { @@ -692,12 +698,11 @@ namespace ts { */ escapedText: __String; originalKeywordKind?: SyntaxKind; // Original syntaxKind which get set so that we can report an error later - /*@internal*/ autoGenerateKind?: GeneratedIdentifierKind; // Specifies whether to auto-generate the text for an identifier. + /*@internal*/ autoGenerateFlags?: GeneratedIdentifierFlags; // Specifies whether to auto-generate the text for an identifier. /*@internal*/ autoGenerateId?: number; // Ensures unique generated identifiers get unique names, but clones get the same name. isInJSDocNamespace?: boolean; // if the node is a member in a JSDoc namespace /*@internal*/ typeArguments?: NodeArray; // Only defined on synthesized nodes. Though not syntactically valid, used in emitting diagnostics, quickinfo, and signature help. /*@internal*/ jsdocDotPos?: number; // Identifier occurs in JSDoc-style generic: Id. - /*@internal*/ skipNameGenerationScope?: boolean; // Should skip a name generation scope when generating the name for this identifier } // Transient identifier node (marked by id === -1) @@ -707,10 +712,7 @@ namespace ts { /*@internal*/ export interface GeneratedIdentifier extends Identifier { - autoGenerateKind: GeneratedIdentifierKind.Auto - | GeneratedIdentifierKind.Loop - | GeneratedIdentifierKind.Unique - | GeneratedIdentifierKind.Node; + autoGenerateFlags: GeneratedIdentifierFlags; } export interface QualifiedName extends Node { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 796ed35a861..357e5179865 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -5134,7 +5134,7 @@ namespace ts { /* @internal */ export function isGeneratedIdentifier(node: Node): node is GeneratedIdentifier { // Using `>` here catches both `GeneratedIdentifierKind.None` and `undefined`. - return isIdentifier(node) && node.autoGenerateKind > GeneratedIdentifierKind.None; + return isIdentifier(node) && (node.autoGenerateFlags & GeneratedIdentifierFlags.KindMask) > GeneratedIdentifierFlags.None; } // Keywords diff --git a/tests/baselines/reference/asyncAwaitNestedClasses_es5.js b/tests/baselines/reference/asyncAwaitNestedClasses_es5.js new file mode 100644 index 00000000000..ff806b76ff1 --- /dev/null +++ b/tests/baselines/reference/asyncAwaitNestedClasses_es5.js @@ -0,0 +1,52 @@ +//// [asyncAwaitNestedClasses_es5.ts] +// https://github.com/Microsoft/TypeScript/issues/20744 +class A { + static B = class B { + static func2(): Promise { + return new Promise((resolve) => { resolve(null); }); + } + static C = class C { + static async func() { + await B.func2(); + } + } + } +} + +A.B.C.func(); + +//// [asyncAwaitNestedClasses_es5.js] +// https://github.com/Microsoft/TypeScript/issues/20744 +var A = /** @class */ (function () { + function A() { + } + A.B = (_a = /** @class */ (function () { + function B() { + } + B.func2 = function () { + return new Promise(function (resolve) { resolve(null); }); + }; + return B; + }()), + _a.C = /** @class */ (function () { + function C() { + } + C.func = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_b) { + switch (_b.label) { + case 0: return [4 /*yield*/, _a.func2()]; + case 1: + _b.sent(); + return [2 /*return*/]; + } + }); + }); + }; + return C; + }()), + _a); + return A; + var _a; +}()); +A.B.C.func(); diff --git a/tests/baselines/reference/asyncAwaitNestedClasses_es5.symbols b/tests/baselines/reference/asyncAwaitNestedClasses_es5.symbols new file mode 100644 index 00000000000..339b2617fbf --- /dev/null +++ b/tests/baselines/reference/asyncAwaitNestedClasses_es5.symbols @@ -0,0 +1,43 @@ +=== tests/cases/conformance/async/es5/asyncAwaitNestedClasses_es5.ts === +// https://github.com/Microsoft/TypeScript/issues/20744 +class A { +>A : Symbol(A, Decl(asyncAwaitNestedClasses_es5.ts, 0, 0)) + + static B = class B { +>B : Symbol(A.B, Decl(asyncAwaitNestedClasses_es5.ts, 1, 9)) +>B : Symbol(B, Decl(asyncAwaitNestedClasses_es5.ts, 2, 14)) + + static func2(): Promise { +>func2 : Symbol(B.func2, Decl(asyncAwaitNestedClasses_es5.ts, 2, 24)) +>Promise : Symbol(Promise, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + return new Promise((resolve) => { resolve(null); }); +>Promise : Symbol(Promise, Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>resolve : Symbol(resolve, Decl(asyncAwaitNestedClasses_es5.ts, 4, 32)) +>resolve : Symbol(resolve, Decl(asyncAwaitNestedClasses_es5.ts, 4, 32)) + } + static C = class C { +>C : Symbol(B.C, Decl(asyncAwaitNestedClasses_es5.ts, 5, 9)) +>C : Symbol(C, Decl(asyncAwaitNestedClasses_es5.ts, 6, 18)) + + static async func() { +>func : Symbol(C.func, Decl(asyncAwaitNestedClasses_es5.ts, 6, 28)) + + await B.func2(); +>B.func2 : Symbol(B.func2, Decl(asyncAwaitNestedClasses_es5.ts, 2, 24)) +>B : Symbol(B, Decl(asyncAwaitNestedClasses_es5.ts, 2, 14)) +>func2 : Symbol(B.func2, Decl(asyncAwaitNestedClasses_es5.ts, 2, 24)) + } + } + } +} + +A.B.C.func(); +>A.B.C.func : Symbol(C.func, Decl(asyncAwaitNestedClasses_es5.ts, 6, 28)) +>A.B.C : Symbol(B.C, Decl(asyncAwaitNestedClasses_es5.ts, 5, 9)) +>A.B : Symbol(A.B, Decl(asyncAwaitNestedClasses_es5.ts, 1, 9)) +>A : Symbol(A, Decl(asyncAwaitNestedClasses_es5.ts, 0, 0)) +>B : Symbol(A.B, Decl(asyncAwaitNestedClasses_es5.ts, 1, 9)) +>C : Symbol(B.C, Decl(asyncAwaitNestedClasses_es5.ts, 5, 9)) +>func : Symbol(C.func, Decl(asyncAwaitNestedClasses_es5.ts, 6, 28)) + diff --git a/tests/baselines/reference/asyncAwaitNestedClasses_es5.types b/tests/baselines/reference/asyncAwaitNestedClasses_es5.types new file mode 100644 index 00000000000..fd61951d5cc --- /dev/null +++ b/tests/baselines/reference/asyncAwaitNestedClasses_es5.types @@ -0,0 +1,52 @@ +=== tests/cases/conformance/async/es5/asyncAwaitNestedClasses_es5.ts === +// https://github.com/Microsoft/TypeScript/issues/20744 +class A { +>A : A + + static B = class B { +>B : typeof B +>class B { static func2(): Promise { return new Promise((resolve) => { resolve(null); }); } static C = class C { static async func() { await B.func2(); } } } : typeof B +>B : typeof B + + static func2(): Promise { +>func2 : () => Promise +>Promise : Promise + + return new Promise((resolve) => { resolve(null); }); +>new Promise((resolve) => { resolve(null); }) : Promise +>Promise : PromiseConstructor +>(resolve) => { resolve(null); } : (resolve: (value?: void | PromiseLike) => void) => void +>resolve : (value?: void | PromiseLike) => void +>resolve(null) : void +>resolve : (value?: void | PromiseLike) => void +>null : null + } + static C = class C { +>C : typeof C +>class C { static async func() { await B.func2(); } } : typeof C +>C : typeof C + + static async func() { +>func : () => Promise + + await B.func2(); +>await B.func2() : void +>B.func2() : Promise +>B.func2 : () => Promise +>B : typeof B +>func2 : () => Promise + } + } + } +} + +A.B.C.func(); +>A.B.C.func() : Promise +>A.B.C.func : () => Promise +>A.B.C : typeof C +>A.B : typeof B +>A : typeof A +>B : typeof B +>C : typeof C +>func : () => Promise + diff --git a/tests/cases/conformance/async/es5/asyncAwaitNestedClasses_es5.ts b/tests/cases/conformance/async/es5/asyncAwaitNestedClasses_es5.ts new file mode 100644 index 00000000000..222718122ef --- /dev/null +++ b/tests/cases/conformance/async/es5/asyncAwaitNestedClasses_es5.ts @@ -0,0 +1,18 @@ +// @target: ES5 +// @lib: es5,es2015.promise +// @noEmitHelpers: true +// https://github.com/Microsoft/TypeScript/issues/20744 +class A { + static B = class B { + static func2(): Promise { + return new Promise((resolve) => { resolve(null); }); + } + static C = class C { + static async func() { + await B.func2(); + } + } + } +} + +A.B.C.func(); \ No newline at end of file From 154c6141f1a8f41b94d44611248662dc070c7c18 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 16 Jan 2018 12:37:15 -0800 Subject: [PATCH 092/172] Allow functions to be printed structurally in declaration emit even when they have symbols (#21203) * Allow functions to be printed structurally in declaration emit even when they have symbols * Implement CR feedback and fix lint --- src/compiler/checker.ts | 8 +++++- src/compiler/declarationEmitter.ts | 6 ++--- src/compiler/types.ts | 4 ++- .../reference/api/tsserverlibrary.d.ts | 4 ++- tests/baselines/reference/api/typescript.d.ts | 4 ++- ...nFunctionTypeNonlocalShouldNotBeAnError.js | 26 +++++++++++++++++++ ...tionTypeNonlocalShouldNotBeAnError.symbols | 15 +++++++++++ ...nctionTypeNonlocalShouldNotBeAnError.types | 16 ++++++++++++ .../privacyCheckTypeOfFunction.errors.txt | 5 +--- ...nFunctionTypeNonlocalShouldNotBeAnError.ts | 8 ++++++ 10 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 tests/baselines/reference/declarationFunctionTypeNonlocalShouldNotBeAnError.js create mode 100644 tests/baselines/reference/declarationFunctionTypeNonlocalShouldNotBeAnError.symbols create mode 100644 tests/baselines/reference/declarationFunctionTypeNonlocalShouldNotBeAnError.types create mode 100644 tests/cases/compiler/declarationFunctionTypeNonlocalShouldNotBeAnError.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7ab50f7be60..c0eda617e0c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2531,6 +2531,11 @@ namespace ts { return access.accessibility === SymbolAccessibility.Accessible; } + function isValueSymbolAccessible(typeSymbol: Symbol, enclosingDeclaration: Node): boolean { + const access = isSymbolAccessible(typeSymbol, enclosingDeclaration, SymbolFlags.Value, /*shouldComputeAliasesToMakeVisible*/ false); + return access.accessibility === SymbolAccessibility.Accessible; + } + /** * Check if the given symbol in given enclosing declaration is accessible and mark all associated alias to be visible if requested * @@ -2993,7 +2998,8 @@ namespace ts { declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { // typeof is allowed only for static/non local functions - return !!(context.flags & NodeBuilderFlags.UseTypeOfFunction) || contains(context.symbolStack, symbol); // it is type of the symbol uses itself recursively + return (!!(context.flags & NodeBuilderFlags.UseTypeOfFunction) || contains(context.symbolStack, symbol)) && // it is type of the symbol uses itself recursively + (!(context.flags & NodeBuilderFlags.UseStructuralFallback) || isValueSymbolAccessible(symbol, context.enclosingDeclaration)); // And the build is going to succeed without visibility error or there is no structural fallback allowed } } } diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index f37ae497f84..aa1ec82a631 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -358,7 +358,7 @@ namespace ts { } else { errorNameNode = declaration.name; - const format = TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.WriteDefaultSymbolWithoutName | + const format = TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseStructuralFallback | TypeFormatFlags.WriteDefaultSymbolWithoutName | TypeFormatFlags.WriteClassExpressionAsTypeLiteral | (shouldUseResolverType ? TypeFormatFlags.AddUndefined : 0); resolver.writeTypeOfDeclaration(declaration, enclosingDeclaration, format, writer); @@ -378,7 +378,7 @@ namespace ts { resolver.writeReturnTypeOfSignatureDeclaration( signature, enclosingDeclaration, - TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.WriteClassExpressionAsTypeLiteral | TypeFormatFlags.WriteDefaultSymbolWithoutName, + TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseStructuralFallback | TypeFormatFlags.WriteClassExpressionAsTypeLiteral | TypeFormatFlags.WriteDefaultSymbolWithoutName, writer); errorNameNode = undefined; } @@ -643,7 +643,7 @@ namespace ts { resolver.writeTypeOfExpression( expr, enclosingDeclaration, - TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.WriteClassExpressionAsTypeLiteral | TypeFormatFlags.WriteDefaultSymbolWithoutName, + TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseStructuralFallback | TypeFormatFlags.WriteClassExpressionAsTypeLiteral | TypeFormatFlags.WriteDefaultSymbolWithoutName, writer); write(";"); writeLine(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6622348e101..ba5c913c4b0 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2934,6 +2934,7 @@ namespace ts { NoTruncation = 1 << 0, // Don't truncate result WriteArrayAsGenericType = 1 << 1, // Write Array instead T[] WriteDefaultSymbolWithoutName = 1 << 2, // Write `default`-named symbols as `default` instead of how they were written + UseStructuralFallback = 1 << 3, // When an alias cannot be named by its symbol, rather than report an error, fallback to a structural printout if possible // empty space WriteTypeArgumentsOfSignature = 1 << 5, // Write the type arguments instead of type parameters of the signature UseFullyQualifiedType = 1 << 6, // Write out the fully qualified type name (eg. Module.Type, instead of Type) @@ -2968,6 +2969,7 @@ namespace ts { NoTruncation = 1 << 0, // Don't truncate typeToString result WriteArrayAsGenericType = 1 << 1, // Write Array instead T[] WriteDefaultSymbolWithoutName = 1 << 2, // Write all `defaut`-named symbols as `default` instead of their written name + UseStructuralFallback = 1 << 3, // When an alias cannot be named by its symbol, rather than report an error, fallback to a structural printout if possible // hole because there's a hole in node builder flags WriteTypeArgumentsOfSignature = 1 << 5, // Write the type arguments instead of type parameters of the signature UseFullyQualifiedType = 1 << 6, // Write out the fully qualified type name (eg. Module.Type, instead of Type) @@ -2997,7 +2999,7 @@ namespace ts { /** @deprecated */ WriteOwnNameForAnyLike = 0, // Does nothing NodeBuilderFlagsMask = - NoTruncation | WriteArrayAsGenericType | WriteDefaultSymbolWithoutName | WriteTypeArgumentsOfSignature | + NoTruncation | WriteArrayAsGenericType | WriteDefaultSymbolWithoutName | UseStructuralFallback | WriteTypeArgumentsOfSignature | UseFullyQualifiedType | SuppressAnyReturnType | MultilineObjectLiterals | WriteClassExpressionAsTypeLiteral | UseTypeOfFunction | OmitParameterModifiers | UseAliasDefinedOutsideCurrentScope | AllowUniqueESSymbolType | InTypeAlias, } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 0a1385e9553..cc8459f8556 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1800,6 +1800,7 @@ declare namespace ts { NoTruncation = 1, WriteArrayAsGenericType = 2, WriteDefaultSymbolWithoutName = 4, + UseStructuralFallback = 8, WriteTypeArgumentsOfSignature = 32, UseFullyQualifiedType = 64, UseOnlyExternalAliasing = 128, @@ -1826,6 +1827,7 @@ declare namespace ts { NoTruncation = 1, WriteArrayAsGenericType = 2, WriteDefaultSymbolWithoutName = 4, + UseStructuralFallback = 8, WriteTypeArgumentsOfSignature = 32, UseFullyQualifiedType = 64, SuppressAnyReturnType = 256, @@ -1842,7 +1844,7 @@ declare namespace ts { InFirstTypeArgument = 4194304, InTypeAlias = 8388608, /** @deprecated */ WriteOwnNameForAnyLike = 0, - NodeBuilderFlagsMask = 9469287, + NodeBuilderFlagsMask = 9469295, } enum SymbolFormatFlags { None = 0, diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 82995f97bfa..a9769934d20 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1800,6 +1800,7 @@ declare namespace ts { NoTruncation = 1, WriteArrayAsGenericType = 2, WriteDefaultSymbolWithoutName = 4, + UseStructuralFallback = 8, WriteTypeArgumentsOfSignature = 32, UseFullyQualifiedType = 64, UseOnlyExternalAliasing = 128, @@ -1826,6 +1827,7 @@ declare namespace ts { NoTruncation = 1, WriteArrayAsGenericType = 2, WriteDefaultSymbolWithoutName = 4, + UseStructuralFallback = 8, WriteTypeArgumentsOfSignature = 32, UseFullyQualifiedType = 64, SuppressAnyReturnType = 256, @@ -1842,7 +1844,7 @@ declare namespace ts { InFirstTypeArgument = 4194304, InTypeAlias = 8388608, /** @deprecated */ WriteOwnNameForAnyLike = 0, - NodeBuilderFlagsMask = 9469287, + NodeBuilderFlagsMask = 9469295, } enum SymbolFormatFlags { None = 0, diff --git a/tests/baselines/reference/declarationFunctionTypeNonlocalShouldNotBeAnError.js b/tests/baselines/reference/declarationFunctionTypeNonlocalShouldNotBeAnError.js new file mode 100644 index 00000000000..6faa0bfca60 --- /dev/null +++ b/tests/baselines/reference/declarationFunctionTypeNonlocalShouldNotBeAnError.js @@ -0,0 +1,26 @@ +//// [declarationFunctionTypeNonlocalShouldNotBeAnError.ts] +namespace foo { + function bar(): void {} + + export const obj = { + bar + } +} + + +//// [declarationFunctionTypeNonlocalShouldNotBeAnError.js] +var foo; +(function (foo) { + function bar() { } + foo.obj = { + bar: bar + }; +})(foo || (foo = {})); + + +//// [declarationFunctionTypeNonlocalShouldNotBeAnError.d.ts] +declare namespace foo { + const obj: { + bar: () => void; + }; +} diff --git a/tests/baselines/reference/declarationFunctionTypeNonlocalShouldNotBeAnError.symbols b/tests/baselines/reference/declarationFunctionTypeNonlocalShouldNotBeAnError.symbols new file mode 100644 index 00000000000..74ce59284e0 --- /dev/null +++ b/tests/baselines/reference/declarationFunctionTypeNonlocalShouldNotBeAnError.symbols @@ -0,0 +1,15 @@ +=== tests/cases/compiler/declarationFunctionTypeNonlocalShouldNotBeAnError.ts === +namespace foo { +>foo : Symbol(foo, Decl(declarationFunctionTypeNonlocalShouldNotBeAnError.ts, 0, 0)) + + function bar(): void {} +>bar : Symbol(bar, Decl(declarationFunctionTypeNonlocalShouldNotBeAnError.ts, 0, 15)) + + export const obj = { +>obj : Symbol(obj, Decl(declarationFunctionTypeNonlocalShouldNotBeAnError.ts, 3, 16)) + + bar +>bar : Symbol(bar, Decl(declarationFunctionTypeNonlocalShouldNotBeAnError.ts, 3, 24)) + } +} + diff --git a/tests/baselines/reference/declarationFunctionTypeNonlocalShouldNotBeAnError.types b/tests/baselines/reference/declarationFunctionTypeNonlocalShouldNotBeAnError.types new file mode 100644 index 00000000000..e86ead184c9 --- /dev/null +++ b/tests/baselines/reference/declarationFunctionTypeNonlocalShouldNotBeAnError.types @@ -0,0 +1,16 @@ +=== tests/cases/compiler/declarationFunctionTypeNonlocalShouldNotBeAnError.ts === +namespace foo { +>foo : typeof foo + + function bar(): void {} +>bar : () => void + + export const obj = { +>obj : { bar: () => void; } +>{ bar } : { bar: () => void; } + + bar +>bar : () => void + } +} + diff --git a/tests/baselines/reference/privacyCheckTypeOfFunction.errors.txt b/tests/baselines/reference/privacyCheckTypeOfFunction.errors.txt index 88801e06dfd..f901d08b462 100644 --- a/tests/baselines/reference/privacyCheckTypeOfFunction.errors.txt +++ b/tests/baselines/reference/privacyCheckTypeOfFunction.errors.txt @@ -1,14 +1,11 @@ tests/cases/compiler/privacyCheckTypeOfFunction.ts(3,22): error TS4025: Exported variable 'x' has or is using private name 'foo'. -tests/cases/compiler/privacyCheckTypeOfFunction.ts(4,12): error TS4025: Exported variable 'b' has or is using private name 'foo'. -==== tests/cases/compiler/privacyCheckTypeOfFunction.ts (2 errors) ==== +==== tests/cases/compiler/privacyCheckTypeOfFunction.ts (1 errors) ==== function foo() { } export var x: typeof foo; ~~~ !!! error TS4025: Exported variable 'x' has or is using private name 'foo'. export var b = foo; - ~ -!!! error TS4025: Exported variable 'b' has or is using private name 'foo'. \ No newline at end of file diff --git a/tests/cases/compiler/declarationFunctionTypeNonlocalShouldNotBeAnError.ts b/tests/cases/compiler/declarationFunctionTypeNonlocalShouldNotBeAnError.ts new file mode 100644 index 00000000000..8d70a435e4d --- /dev/null +++ b/tests/cases/compiler/declarationFunctionTypeNonlocalShouldNotBeAnError.ts @@ -0,0 +1,8 @@ +// @declaration: true +namespace foo { + function bar(): void {} + + export const obj = { + bar + } +} From 136c4d0dda5a381957f14b6c63456d2766053108 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 16 Jan 2018 15:02:23 -0800 Subject: [PATCH 093/172] Fixes var declaration shadowing in async functions --- src/compiler/core.ts | 3 +- src/compiler/transformers/es2017.ts | 278 ++++++++++- .../reference/asyncWithVarShadowing_es6.js | 463 ++++++++++++++++++ .../asyncWithVarShadowing_es6.symbols | 406 +++++++++++++++ .../reference/asyncWithVarShadowing_es6.types | 410 ++++++++++++++++ .../async/es6/asyncWithVarShadowing_es6.ts | 207 ++++++++ 6 files changed, 1752 insertions(+), 15 deletions(-) create mode 100644 tests/baselines/reference/asyncWithVarShadowing_es6.js create mode 100644 tests/baselines/reference/asyncWithVarShadowing_es6.symbols create mode 100644 tests/baselines/reference/asyncWithVarShadowing_es6.types create mode 100644 tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 0faabbda2a0..fe63b38ac1b 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1341,7 +1341,8 @@ namespace ts { export function cloneMap(map: SymbolTable): SymbolTable; export function cloneMap(map: ReadonlyMap): Map; - export function cloneMap(map: ReadonlyMap | SymbolTable): Map | SymbolTable { + export function cloneMap(map: ReadonlyUnderscoreEscapedMap): UnderscoreEscapedMap; + export function cloneMap(map: ReadonlyMap | ReadonlyUnderscoreEscapedMap | SymbolTable): Map | UnderscoreEscapedMap | SymbolTable { const clone = createMap(); copyEntries(map as Map, clone); return clone; diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index 9fa39da2d23..5733d089a7d 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -12,9 +12,9 @@ namespace ts { export function transformES2017(context: TransformationContext) { const { - startLexicalEnvironment, resumeLexicalEnvironment, - endLexicalEnvironment + endLexicalEnvironment, + hoistVariableDeclaration } = context; const resolver = context.getEmitResolver(); @@ -33,6 +33,8 @@ namespace ts { */ let enclosingSuperContainerFlags: NodeCheckFlags = 0; + let enclosingFunctionParameterNames: UnderscoreEscapedMap; + // Save the previous transformation hooks. const previousOnEmitNode = context.onEmitNode; const previousOnSubstituteNode = context.onSubstituteNode; @@ -83,6 +85,103 @@ namespace ts { } } + function asyncBodyVisitor(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.VariableStatement: + return visitVariableStatementInAsyncBody(node); + case SyntaxKind.ForStatement: + return visitForStatementInAsyncBody(node); + case SyntaxKind.ForInStatement: + return visitForInStatementInAsyncBody(node); + case SyntaxKind.ForOfStatement: + return visitForOfStatementInAsyncBody(node); + case SyntaxKind.Block: + case SyntaxKind.SwitchStatement: + case SyntaxKind.CaseBlock: + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + case SyntaxKind.TryStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.IfStatement: + case SyntaxKind.WithStatement: + return visitEachChild(node, asyncBodyVisitor, context); + case SyntaxKind.CatchClause: + return visitCatchClauseInAsyncBody(node); + } + return visitor(node); + } + + function visitCatchClauseInAsyncBody(node: CatchClause) { + const catchClauseNames = createUnderscoreEscapedMap(); + recordDeclarationName(node.variableDeclaration, catchClauseNames); + + // names declared in a catch variable are block scoped + let catchClauseUnshadowedNames: UnderscoreEscapedMap; + catchClauseNames.forEach((_, escapedName) => { + if (enclosingFunctionParameterNames.has(escapedName)) { + if (!catchClauseUnshadowedNames) { + catchClauseUnshadowedNames = cloneMap(enclosingFunctionParameterNames); + } + catchClauseUnshadowedNames.delete(escapedName); + } + }); + + if (catchClauseUnshadowedNames) { + const savedEnclosingFunctionParameterNames = enclosingFunctionParameterNames; + enclosingFunctionParameterNames = catchClauseUnshadowedNames; + const result = visitEachChild(node, asyncBodyVisitor, context); + enclosingFunctionParameterNames = savedEnclosingFunctionParameterNames; + return result; + } + else { + return visitEachChild(node, asyncBodyVisitor, context); + } + } + + function visitVariableStatementInAsyncBody(node: VariableStatement) { + if (isVariableDeclarationListWithCollidingName(node.declarationList)) { + const expression = visitVariableDeclarationListWithCollidingNames(node.declarationList, /*hasReceiver*/ false); + return expression ? createStatement(expression) : undefined; + } + return visitEachChild(node, visitor, context); + } + + function visitForInStatementInAsyncBody(node: ForInStatement) { + return updateForIn( + node, + isVariableDeclarationListWithCollidingName(node.initializer) + ? visitVariableDeclarationListWithCollidingNames(node.initializer, /*hasReceiver*/ true) + : visitNode(node.initializer, visitor, isForInitializer), + visitNode(node.expression, visitor, isExpression), + visitNode(node.statement, asyncBodyVisitor, isStatement, liftToBlock) + ); + } + + function visitForOfStatementInAsyncBody(node: ForOfStatement) { + return updateForOf( + node, + visitNode(node.awaitModifier, visitor, isToken), + isVariableDeclarationListWithCollidingName(node.initializer) + ? visitVariableDeclarationListWithCollidingNames(node.initializer, /*hasReceiver*/ true) + : visitNode(node.initializer, visitor, isForInitializer), + visitNode(node.expression, visitor, isExpression), + visitNode(node.statement, asyncBodyVisitor, isStatement, liftToBlock) + ); + } + + function visitForStatementInAsyncBody(node: ForStatement) { + return updateFor( + node, + isVariableDeclarationListWithCollidingName(node.initializer) + ? visitVariableDeclarationListWithCollidingNames(node.initializer, /*hasReceiver*/ false) + : visitNode(node.initializer, visitor, isForInitializer), + visitNode(node.condition, visitor, isExpression), + visitNode(node.incrementor, visitor, isExpression), + visitNode((node).statement, asyncBodyVisitor, isStatement, liftToBlock) + ); + } + /** * Visits an AwaitExpression node. * @@ -197,6 +296,149 @@ namespace ts { ); } + function recordDeclarationName({ name }: ParameterDeclaration | VariableDeclaration | BindingElement, names: UnderscoreEscapedMap) { + if (isIdentifier(name)) { + names.set(name.escapedText, true); + } + else { + for (const element of name.elements) { + if (!isOmittedExpression(element)) { + recordDeclarationName(element, names); + } + } + } + } + + function isVariableDeclarationListWithCollidingName(node: ForInitializer): node is VariableDeclarationList { + return node + && isVariableDeclarationList(node) + && !(node.flags & NodeFlags.BlockScoped) + && forEach(node.declarations, collidesWithParameterName); + } + + function visitVariableDeclarationListWithCollidingNames(node: VariableDeclarationList, hasReceiver: boolean) { + hoistVariableDeclarationList(node); + + const variables = getInitializedVariables(node); + if (variables.length === 0) { + if (hasReceiver) { + return convertBindingNameToAssignmentTarget(node.declarations[0].name); + } + return undefined; + } + + return inlineExpressions(map(variables, transformInitializedVariable)); + } + + function hoistVariableDeclarationList(node: VariableDeclarationList) { + forEach(node.declarations, hoistVariable); + } + + function hoistVariable({ name }: VariableDeclaration | BindingElement) { + if (isIdentifier(name)) { + hoistVariableDeclaration(name); + } + else { + for (const element of name.elements) { + if (!isOmittedExpression(element)) { + hoistVariable(element); + } + } + } + } + + function transformInitializedVariable(node: VariableDeclaration) { + return setSourceMapRange( + createAssignment( + convertBindingNameToAssignmentTarget(node.name), + visitNode(node.initializer, visitor, isExpression) + ), + node + ); + } + + function convertBindingNameToAssignmentTarget(node: BindingName): Identifier | AssignmentPattern { + return isObjectBindingPattern(node) ? convertObjectBindingPatternToObjectAssignmentPattern(node) : + isArrayBindingPattern(node) ? convertArrayBindingPatternToArrayAssignmentPattern(node) : + setSourceMapRange(getSynthesizedClone(node), node); + } + + function convertObjectBindingPatternToObjectAssignmentPattern(node: ObjectBindingPattern): ObjectLiteralExpression { + return createObjectLiteral( + map(node.elements, convertObjectBindingElementToObjectAssignmentElement) + ); + } + + function convertObjectBindingElementToObjectAssignmentElement(node: BindingElement): ObjectLiteralElementLike { + if (node.propertyName) { + let expression: Expression = convertBindingNameToAssignmentTarget(node.name); + if (node.initializer) { + expression = createAssignment(expression, visitNode(node.initializer, visitor, isExpression)); + } + return setSourceMapRange( + createPropertyAssignment( + visitNode(node.propertyName, visitor, isPropertyName), + expression + ), + node + ); + } + else if (node.dotDotDotToken) { + return setSourceMapRange( + createSpreadAssignment( + visitNode(cast(node.name, isIdentifier), visitor, isIdentifier) + ), + node + ); + } + else { + return setSourceMapRange( + createShorthandPropertyAssignment( + visitNode(cast(node.name, isIdentifier), visitor, isIdentifier), + visitNode(node.initializer, visitor, isExpression) + ), + node + ); + } + } + + function convertArrayBindingPatternToArrayAssignmentPattern(node: ArrayBindingPattern): ArrayLiteralExpression { + return setSourceMapRange( + createArrayLiteral( + map(node.elements, convertArrayBindingElementToArrayAssignmentElement) + ), + node + ); + } + + function convertArrayBindingElementToArrayAssignmentElement(node: ArrayBindingElement): Expression { + if (isOmittedExpression(node)) return node; + let expression: Expression = convertBindingNameToAssignmentTarget(node.name); + if (node.initializer) { + expression = createAssignment(expression, visitNode(node.initializer, visitor, isExpression)); + setSourceMapRange(expression, node); + } + else if (node.dotDotDotToken) { + expression = createSpread(expression); + setSourceMapRange(expression, node); + } + return expression; + } + + function collidesWithParameterName({ name }: VariableDeclaration | BindingElement): boolean { + if (isIdentifier(name)) { + return enclosingFunctionParameterNames.has(name.escapedText); + } + else { + for (const element of name.elements) { + if (!isOmittedExpression(element) && collidesWithParameterName(element)) { + return true; + } + } + } + return false; + } + function transformAsyncFunctionBody(node: MethodDeclaration | AccessorDeclaration | FunctionDeclaration | FunctionExpression): FunctionBody; function transformAsyncFunctionBody(node: ArrowFunction): ConciseBody; function transformAsyncFunctionBody(node: FunctionLikeDeclaration): ConciseBody { @@ -214,6 +456,13 @@ namespace ts { // passed to `__awaiter` is executed inside of the callback to the // promise constructor. + const savedEnclosingFunctionParameterNames = enclosingFunctionParameterNames; + enclosingFunctionParameterNames = createUnderscoreEscapedMap(); + for (const parameter of node.parameters) { + recordDeclarationName(parameter, enclosingFunctionParameterNames); + } + + let result: ConciseBody; if (!isArrowFunction) { const statements: Statement[] = []; const statementOffset = addPrologue(statements, (node.body).statements, /*ensureUseStrict*/ false, visitor); @@ -223,7 +472,7 @@ namespace ts { context, hasLexicalArguments, promiseConstructor, - transformFunctionBodyWorker(node.body, statementOffset) + transformAsyncFunctionBodyWorker(node.body, statementOffset) ) ) ); @@ -246,35 +495,36 @@ namespace ts { } } - return block; + result = block; } else { const expression = createAwaiterHelper( context, hasLexicalArguments, promiseConstructor, - transformFunctionBodyWorker(node.body) + transformAsyncFunctionBodyWorker(node.body) ); const declarations = endLexicalEnvironment(); if (some(declarations)) { const block = convertToFunctionBody(expression); - return updateBlock(block, setTextRange(createNodeArray(concatenate(block.statements, declarations)), block.statements)); + result = updateBlock(block, setTextRange(createNodeArray(concatenate(block.statements, declarations)), block.statements)); + } + else { + result = expression; } - - return expression; } + + enclosingFunctionParameterNames = savedEnclosingFunctionParameterNames; + return result; } - function transformFunctionBodyWorker(body: ConciseBody, start?: number) { + function transformAsyncFunctionBodyWorker(body: ConciseBody, start?: number) { if (isBlock(body)) { - return updateBlock(body, visitLexicalEnvironment(body.statements, visitor, context, start)); + return updateBlock(body, visitNodes(body.statements, asyncBodyVisitor, isStatement, start)); } else { - startLexicalEnvironment(); - const visited = convertToFunctionBody(visitNode(body, visitor, isConciseBody)); - const declarations = endLexicalEnvironment(); - return updateBlock(visited, setTextRange(createNodeArray(concatenate(visited.statements, declarations)), visited.statements)); + return convertToFunctionBody(visitNode(body, asyncBodyVisitor, isConciseBody)); } } diff --git a/tests/baselines/reference/asyncWithVarShadowing_es6.js b/tests/baselines/reference/asyncWithVarShadowing_es6.js new file mode 100644 index 00000000000..726bf935cf6 --- /dev/null +++ b/tests/baselines/reference/asyncWithVarShadowing_es6.js @@ -0,0 +1,463 @@ +//// [asyncWithVarShadowing_es6.ts] +// https://github.com/Microsoft/TypeScript/issues/20461 +declare const y: any; + +async function fn1(x) { + var x; +} + +async function fn2(x) { + var x, z; +} + +async function fn3(x) { + var z; +} + +async function fn4(x) { + var x = y; +} + +async function fn5(x) { + var { x } = y; +} + +async function fn6(x) { + var { x, z } = y; +} + +async function fn7(x) { + var { x = y } = y; +} + +async function fn8(x) { + var { z: x } = y; +} + +async function fn9(x) { + var { z: { x } } = y; +} + +async function fn10(x) { + var { z: { x } = y } = y; +} + +async function fn11(x) { + var { ...x } = y; +} + +async function fn12(x) { + var [x] = y; +} + +async function fn13(x) { + var [x = y] = y; +} + +async function fn14(x) { + var [, x] = y; +} + +async function fn15(x) { + var [...x] = y; +} + +async function fn16(x) { + var [[x]] = y; +} + +async function fn17(x) { + var [[x] = y] = y; +} + +async function fn18({ x }) { + var x; +} + +async function fn19([x]) { + var x; +} + +async function fn20(x) { + { + var x; + } +} + +async function fn21(x) { + if (y) { + var x; + } +} + +async function fn22(x) { + if (y) { + } + else { + var x; + } +} + +async function fn23(x) { + try { + var x; + } + catch (e) { + } +} + +async function fn24(x) { + try { + + } + catch (e) { + var x; + } +} + +async function fn25(x) { + try { + + } + catch (x) { + var x; + } +} + +async function fn26(x) { + try { + + } + catch ({ x }) { + var x; + } +} + +async function fn27(x) { + try { + } + finally { + var x; + } +} + +async function fn28(x) { + while (y) { + var x; + } +} + +async function fn29(x) { + do { + var x; + } + while (y); +} + +async function fn30(x) { + for (var x = y;;) { + + } +} + +async function fn31(x) { + for (var { x } = y;;) { + } +} + +async function fn32(x) { + for (;;) { + var x; + } +} + +async function fn33(x: string) { + for (var x in y) { + } +} + +async function fn34(x) { + for (var z in y) { + var x; + } +} + +async function fn35(x) { + for (var x of y) { + } +} + +async function fn36(x) { + for (var { x } of y) { + } +} + +async function fn37(x) { + for (var z of y) { + var x; + } +} + +async function fn38(x) { + switch (y) { + case y: + var x; + } +} + +//// [asyncWithVarShadowing_es6.js] +function fn1(x) { + return __awaiter(this, void 0, void 0, function* () { + }); + var x; +} +function fn2(x) { + return __awaiter(this, void 0, void 0, function* () { + }); + var x, z; +} +function fn3(x) { + return __awaiter(this, void 0, void 0, function* () { + var z; + }); +} +function fn4(x) { + return __awaiter(this, void 0, void 0, function* () { + x = y; + }); + var x; +} +function fn5(x) { + return __awaiter(this, void 0, void 0, function* () { + ({ x } = y); + }); + var x; +} +function fn6(x) { + return __awaiter(this, void 0, void 0, function* () { + ({ x, z } = y); + }); + var x, z; +} +function fn7(x) { + return __awaiter(this, void 0, void 0, function* () { + ({ x = y } = y); + }); + var x; +} +function fn8(x) { + return __awaiter(this, void 0, void 0, function* () { + ({ z: x } = y); + }); + var x; +} +function fn9(x) { + return __awaiter(this, void 0, void 0, function* () { + ({ z: { x } } = y); + }); + var x; +} +function fn10(x) { + return __awaiter(this, void 0, void 0, function* () { + ({ z: { x } = y } = y); + }); + var x; +} +function fn11(x) { + return __awaiter(this, void 0, void 0, function* () { + x = __rest(y, []); + }); + var x; +} +function fn12(x) { + return __awaiter(this, void 0, void 0, function* () { + [x] = y; + }); + var x; +} +function fn13(x) { + return __awaiter(this, void 0, void 0, function* () { + [x = y] = y; + }); + var x; +} +function fn14(x) { + return __awaiter(this, void 0, void 0, function* () { + [, x] = y; + }); + var x; +} +function fn15(x) { + return __awaiter(this, void 0, void 0, function* () { + [...x] = y; + }); + var x; +} +function fn16(x) { + return __awaiter(this, void 0, void 0, function* () { + [[x]] = y; + }); + var x; +} +function fn17(x) { + return __awaiter(this, void 0, void 0, function* () { + [[x] = y] = y; + }); + var x; +} +function fn18({ x }) { + return __awaiter(this, void 0, void 0, function* () { + }); + var x; +} +function fn19([x]) { + return __awaiter(this, void 0, void 0, function* () { + }); + var x; +} +function fn20(x) { + return __awaiter(this, void 0, void 0, function* () { + { + } + }); + var x; +} +function fn21(x) { + return __awaiter(this, void 0, void 0, function* () { + if (y) { + } + }); + var x; +} +function fn22(x) { + return __awaiter(this, void 0, void 0, function* () { + if (y) { + } + else { + } + }); + var x; +} +function fn23(x) { + return __awaiter(this, void 0, void 0, function* () { + try { + } + catch (e) { + } + }); + var x; +} +function fn24(x) { + return __awaiter(this, void 0, void 0, function* () { + try { + } + catch (e) { + } + }); + var x; +} +function fn25(x) { + return __awaiter(this, void 0, void 0, function* () { + try { + } + catch (x) { + var x; + } + }); +} +function fn26(x) { + return __awaiter(this, void 0, void 0, function* () { + try { + } + catch ({ x }) { + var x; + } + }); +} +function fn27(x) { + return __awaiter(this, void 0, void 0, function* () { + try { + } + finally { + } + }); + var x; +} +function fn28(x) { + return __awaiter(this, void 0, void 0, function* () { + while (y) { + } + }); + var x; +} +function fn29(x) { + return __awaiter(this, void 0, void 0, function* () { + do { + } while (y); + }); + var x; +} +function fn30(x) { + return __awaiter(this, void 0, void 0, function* () { + for (x = y;;) { + } + }); + var x; +} +function fn31(x) { + return __awaiter(this, void 0, void 0, function* () { + for ({ x } = y;;) { + } + }); + var x; +} +function fn32(x) { + return __awaiter(this, void 0, void 0, function* () { + for (;;) { + } + }); + var x; +} +function fn33(x) { + return __awaiter(this, void 0, void 0, function* () { + for (x in y) { + } + }); + var x; +} +function fn34(x) { + return __awaiter(this, void 0, void 0, function* () { + for (var z in y) { + } + }); + var x; +} +function fn35(x) { + return __awaiter(this, void 0, void 0, function* () { + for (x of y) { + } + }); + var x; +} +function fn36(x) { + return __awaiter(this, void 0, void 0, function* () { + for ({ x } of y) { + } + }); + var x; +} +function fn37(x) { + return __awaiter(this, void 0, void 0, function* () { + for (var z of y) { + } + }); + var x; +} +function fn38(x) { + return __awaiter(this, void 0, void 0, function* () { + switch (y) { + case y: + } + }); + var x; +} diff --git a/tests/baselines/reference/asyncWithVarShadowing_es6.symbols b/tests/baselines/reference/asyncWithVarShadowing_es6.symbols new file mode 100644 index 00000000000..aec43c553a5 --- /dev/null +++ b/tests/baselines/reference/asyncWithVarShadowing_es6.symbols @@ -0,0 +1,406 @@ +=== tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts === +// https://github.com/Microsoft/TypeScript/issues/20461 +declare const y: any; +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + +async function fn1(x) { +>fn1 : Symbol(fn1, Decl(asyncWithVarShadowing_es6.ts, 1, 21)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 3, 19), Decl(asyncWithVarShadowing_es6.ts, 4, 7)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 3, 19), Decl(asyncWithVarShadowing_es6.ts, 4, 7)) +} + +async function fn2(x) { +>fn2 : Symbol(fn2, Decl(asyncWithVarShadowing_es6.ts, 5, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 7, 19), Decl(asyncWithVarShadowing_es6.ts, 8, 7)) + + var x, z; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 7, 19), Decl(asyncWithVarShadowing_es6.ts, 8, 7)) +>z : Symbol(z, Decl(asyncWithVarShadowing_es6.ts, 8, 10)) +} + +async function fn3(x) { +>fn3 : Symbol(fn3, Decl(asyncWithVarShadowing_es6.ts, 9, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 11, 19)) + + var z; +>z : Symbol(z, Decl(asyncWithVarShadowing_es6.ts, 12, 7)) +} + +async function fn4(x) { +>fn4 : Symbol(fn4, Decl(asyncWithVarShadowing_es6.ts, 13, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 15, 19), Decl(asyncWithVarShadowing_es6.ts, 16, 7)) + + var x = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 15, 19), Decl(asyncWithVarShadowing_es6.ts, 16, 7)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn5(x) { +>fn5 : Symbol(fn5, Decl(asyncWithVarShadowing_es6.ts, 17, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 19, 19), Decl(asyncWithVarShadowing_es6.ts, 20, 9)) + + var { x } = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 19, 19), Decl(asyncWithVarShadowing_es6.ts, 20, 9)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn6(x) { +>fn6 : Symbol(fn6, Decl(asyncWithVarShadowing_es6.ts, 21, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 23, 19), Decl(asyncWithVarShadowing_es6.ts, 24, 9)) + + var { x, z } = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 23, 19), Decl(asyncWithVarShadowing_es6.ts, 24, 9)) +>z : Symbol(z, Decl(asyncWithVarShadowing_es6.ts, 24, 12)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn7(x) { +>fn7 : Symbol(fn7, Decl(asyncWithVarShadowing_es6.ts, 25, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 27, 19), Decl(asyncWithVarShadowing_es6.ts, 28, 9)) + + var { x = y } = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 27, 19), Decl(asyncWithVarShadowing_es6.ts, 28, 9)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn8(x) { +>fn8 : Symbol(fn8, Decl(asyncWithVarShadowing_es6.ts, 29, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 31, 19), Decl(asyncWithVarShadowing_es6.ts, 32, 9)) + + var { z: x } = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 31, 19), Decl(asyncWithVarShadowing_es6.ts, 32, 9)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn9(x) { +>fn9 : Symbol(fn9, Decl(asyncWithVarShadowing_es6.ts, 33, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 35, 19), Decl(asyncWithVarShadowing_es6.ts, 36, 14)) + + var { z: { x } } = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 35, 19), Decl(asyncWithVarShadowing_es6.ts, 36, 14)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn10(x) { +>fn10 : Symbol(fn10, Decl(asyncWithVarShadowing_es6.ts, 37, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 39, 20), Decl(asyncWithVarShadowing_es6.ts, 40, 14)) + + var { z: { x } = y } = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 39, 20), Decl(asyncWithVarShadowing_es6.ts, 40, 14)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn11(x) { +>fn11 : Symbol(fn11, Decl(asyncWithVarShadowing_es6.ts, 41, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 43, 20), Decl(asyncWithVarShadowing_es6.ts, 44, 9)) + + var { ...x } = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 43, 20), Decl(asyncWithVarShadowing_es6.ts, 44, 9)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn12(x) { +>fn12 : Symbol(fn12, Decl(asyncWithVarShadowing_es6.ts, 45, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 47, 20), Decl(asyncWithVarShadowing_es6.ts, 48, 9)) + + var [x] = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 47, 20), Decl(asyncWithVarShadowing_es6.ts, 48, 9)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn13(x) { +>fn13 : Symbol(fn13, Decl(asyncWithVarShadowing_es6.ts, 49, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 51, 20), Decl(asyncWithVarShadowing_es6.ts, 52, 9)) + + var [x = y] = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 51, 20), Decl(asyncWithVarShadowing_es6.ts, 52, 9)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn14(x) { +>fn14 : Symbol(fn14, Decl(asyncWithVarShadowing_es6.ts, 53, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 55, 20), Decl(asyncWithVarShadowing_es6.ts, 56, 10)) + + var [, x] = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 55, 20), Decl(asyncWithVarShadowing_es6.ts, 56, 10)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn15(x) { +>fn15 : Symbol(fn15, Decl(asyncWithVarShadowing_es6.ts, 57, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 59, 20), Decl(asyncWithVarShadowing_es6.ts, 60, 9)) + + var [...x] = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 59, 20), Decl(asyncWithVarShadowing_es6.ts, 60, 9)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn16(x) { +>fn16 : Symbol(fn16, Decl(asyncWithVarShadowing_es6.ts, 61, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 63, 20), Decl(asyncWithVarShadowing_es6.ts, 64, 10)) + + var [[x]] = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 63, 20), Decl(asyncWithVarShadowing_es6.ts, 64, 10)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn17(x) { +>fn17 : Symbol(fn17, Decl(asyncWithVarShadowing_es6.ts, 65, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 67, 20), Decl(asyncWithVarShadowing_es6.ts, 68, 10)) + + var [[x] = y] = y; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 67, 20), Decl(asyncWithVarShadowing_es6.ts, 68, 10)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn18({ x }) { +>fn18 : Symbol(fn18, Decl(asyncWithVarShadowing_es6.ts, 69, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 71, 21), Decl(asyncWithVarShadowing_es6.ts, 72, 7)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 71, 21), Decl(asyncWithVarShadowing_es6.ts, 72, 7)) +} + +async function fn19([x]) { +>fn19 : Symbol(fn19, Decl(asyncWithVarShadowing_es6.ts, 73, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 75, 21), Decl(asyncWithVarShadowing_es6.ts, 76, 7)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 75, 21), Decl(asyncWithVarShadowing_es6.ts, 76, 7)) +} + +async function fn20(x) { +>fn20 : Symbol(fn20, Decl(asyncWithVarShadowing_es6.ts, 77, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 79, 20), Decl(asyncWithVarShadowing_es6.ts, 81, 11)) + { + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 79, 20), Decl(asyncWithVarShadowing_es6.ts, 81, 11)) + } +} + +async function fn21(x) { +>fn21 : Symbol(fn21, Decl(asyncWithVarShadowing_es6.ts, 83, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 85, 20), Decl(asyncWithVarShadowing_es6.ts, 87, 11)) + + if (y) { +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 85, 20), Decl(asyncWithVarShadowing_es6.ts, 87, 11)) + } +} + +async function fn22(x) { +>fn22 : Symbol(fn22, Decl(asyncWithVarShadowing_es6.ts, 89, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 91, 20), Decl(asyncWithVarShadowing_es6.ts, 95, 11)) + + if (y) { +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + } + else { + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 91, 20), Decl(asyncWithVarShadowing_es6.ts, 95, 11)) + } +} + +async function fn23(x) { +>fn23 : Symbol(fn23, Decl(asyncWithVarShadowing_es6.ts, 97, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 99, 20), Decl(asyncWithVarShadowing_es6.ts, 101, 11)) + + try { + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 99, 20), Decl(asyncWithVarShadowing_es6.ts, 101, 11)) + } + catch (e) { +>e : Symbol(e, Decl(asyncWithVarShadowing_es6.ts, 103, 11)) + } +} + +async function fn24(x) { +>fn24 : Symbol(fn24, Decl(asyncWithVarShadowing_es6.ts, 105, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 107, 20), Decl(asyncWithVarShadowing_es6.ts, 112, 11)) + + try { + + } + catch (e) { +>e : Symbol(e, Decl(asyncWithVarShadowing_es6.ts, 111, 11)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 107, 20), Decl(asyncWithVarShadowing_es6.ts, 112, 11)) + } +} + +async function fn25(x) { +>fn25 : Symbol(fn25, Decl(asyncWithVarShadowing_es6.ts, 114, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 116, 20), Decl(asyncWithVarShadowing_es6.ts, 121, 11)) + + try { + + } + catch (x) { +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 120, 11)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 116, 20), Decl(asyncWithVarShadowing_es6.ts, 121, 11)) + } +} + +async function fn26(x) { +>fn26 : Symbol(fn26, Decl(asyncWithVarShadowing_es6.ts, 123, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 125, 20), Decl(asyncWithVarShadowing_es6.ts, 130, 11)) + + try { + + } + catch ({ x }) { +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 129, 12)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 125, 20), Decl(asyncWithVarShadowing_es6.ts, 130, 11)) + } +} + +async function fn27(x) { +>fn27 : Symbol(fn27, Decl(asyncWithVarShadowing_es6.ts, 132, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 134, 20), Decl(asyncWithVarShadowing_es6.ts, 138, 11)) + + try { + } + finally { + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 134, 20), Decl(asyncWithVarShadowing_es6.ts, 138, 11)) + } +} + +async function fn28(x) { +>fn28 : Symbol(fn28, Decl(asyncWithVarShadowing_es6.ts, 140, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 142, 20), Decl(asyncWithVarShadowing_es6.ts, 144, 11)) + + while (y) { +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 142, 20), Decl(asyncWithVarShadowing_es6.ts, 144, 11)) + } +} + +async function fn29(x) { +>fn29 : Symbol(fn29, Decl(asyncWithVarShadowing_es6.ts, 146, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 148, 20), Decl(asyncWithVarShadowing_es6.ts, 150, 11)) + + do { + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 148, 20), Decl(asyncWithVarShadowing_es6.ts, 150, 11)) + } + while (y); +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) +} + +async function fn30(x) { +>fn30 : Symbol(fn30, Decl(asyncWithVarShadowing_es6.ts, 153, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 155, 20), Decl(asyncWithVarShadowing_es6.ts, 156, 12)) + + for (var x = y;;) { +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 155, 20), Decl(asyncWithVarShadowing_es6.ts, 156, 12)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + + } +} + +async function fn31(x) { +>fn31 : Symbol(fn31, Decl(asyncWithVarShadowing_es6.ts, 159, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 161, 20), Decl(asyncWithVarShadowing_es6.ts, 162, 14)) + + for (var { x } = y;;) { +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 161, 20), Decl(asyncWithVarShadowing_es6.ts, 162, 14)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + } +} + +async function fn32(x) { +>fn32 : Symbol(fn32, Decl(asyncWithVarShadowing_es6.ts, 164, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 166, 20), Decl(asyncWithVarShadowing_es6.ts, 168, 11)) + + for (;;) { + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 166, 20), Decl(asyncWithVarShadowing_es6.ts, 168, 11)) + } +} + +async function fn33(x: string) { +>fn33 : Symbol(fn33, Decl(asyncWithVarShadowing_es6.ts, 170, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 172, 20), Decl(asyncWithVarShadowing_es6.ts, 173, 12)) + + for (var x in y) { +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 172, 20), Decl(asyncWithVarShadowing_es6.ts, 173, 12)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + } +} + +async function fn34(x) { +>fn34 : Symbol(fn34, Decl(asyncWithVarShadowing_es6.ts, 175, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 177, 20), Decl(asyncWithVarShadowing_es6.ts, 179, 11)) + + for (var z in y) { +>z : Symbol(z, Decl(asyncWithVarShadowing_es6.ts, 178, 12)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 177, 20), Decl(asyncWithVarShadowing_es6.ts, 179, 11)) + } +} + +async function fn35(x) { +>fn35 : Symbol(fn35, Decl(asyncWithVarShadowing_es6.ts, 181, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 183, 20), Decl(asyncWithVarShadowing_es6.ts, 184, 12)) + + for (var x of y) { +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 183, 20), Decl(asyncWithVarShadowing_es6.ts, 184, 12)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + } +} + +async function fn36(x) { +>fn36 : Symbol(fn36, Decl(asyncWithVarShadowing_es6.ts, 186, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 188, 20), Decl(asyncWithVarShadowing_es6.ts, 189, 14)) + + for (var { x } of y) { +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 188, 20), Decl(asyncWithVarShadowing_es6.ts, 189, 14)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + } +} + +async function fn37(x) { +>fn37 : Symbol(fn37, Decl(asyncWithVarShadowing_es6.ts, 191, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 193, 20), Decl(asyncWithVarShadowing_es6.ts, 195, 11)) + + for (var z of y) { +>z : Symbol(z, Decl(asyncWithVarShadowing_es6.ts, 194, 12)) +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 193, 20), Decl(asyncWithVarShadowing_es6.ts, 195, 11)) + } +} + +async function fn38(x) { +>fn38 : Symbol(fn38, Decl(asyncWithVarShadowing_es6.ts, 197, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 199, 20), Decl(asyncWithVarShadowing_es6.ts, 202, 15)) + + switch (y) { +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + + case y: +>y : Symbol(y, Decl(asyncWithVarShadowing_es6.ts, 1, 13)) + + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 199, 20), Decl(asyncWithVarShadowing_es6.ts, 202, 15)) + } +} diff --git a/tests/baselines/reference/asyncWithVarShadowing_es6.types b/tests/baselines/reference/asyncWithVarShadowing_es6.types new file mode 100644 index 00000000000..42a81f168b0 --- /dev/null +++ b/tests/baselines/reference/asyncWithVarShadowing_es6.types @@ -0,0 +1,410 @@ +=== tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts === +// https://github.com/Microsoft/TypeScript/issues/20461 +declare const y: any; +>y : any + +async function fn1(x) { +>fn1 : (x: any) => Promise +>x : any + + var x; +>x : any +} + +async function fn2(x) { +>fn2 : (x: any) => Promise +>x : any + + var x, z; +>x : any +>z : any +} + +async function fn3(x) { +>fn3 : (x: any) => Promise +>x : any + + var z; +>z : any +} + +async function fn4(x) { +>fn4 : (x: any) => Promise +>x : any + + var x = y; +>x : any +>y : any +} + +async function fn5(x) { +>fn5 : (x: any) => Promise +>x : any + + var { x } = y; +>x : any +>y : any +} + +async function fn6(x) { +>fn6 : (x: any) => Promise +>x : any + + var { x, z } = y; +>x : any +>z : any +>y : any +} + +async function fn7(x) { +>fn7 : (x: any) => Promise +>x : any + + var { x = y } = y; +>x : any +>y : any +>y : any +} + +async function fn8(x) { +>fn8 : (x: any) => Promise +>x : any + + var { z: x } = y; +>z : any +>x : any +>y : any +} + +async function fn9(x) { +>fn9 : (x: any) => Promise +>x : any + + var { z: { x } } = y; +>z : any +>x : any +>y : any +} + +async function fn10(x) { +>fn10 : (x: any) => Promise +>x : any + + var { z: { x } = y } = y; +>z : any +>x : any +>y : any +>y : any +} + +async function fn11(x) { +>fn11 : (x: any) => Promise +>x : any + + var { ...x } = y; +>x : any +>y : any +} + +async function fn12(x) { +>fn12 : (x: any) => Promise +>x : any + + var [x] = y; +>x : any +>y : any +} + +async function fn13(x) { +>fn13 : (x: any) => Promise +>x : any + + var [x = y] = y; +>x : any +>y : any +>y : any +} + +async function fn14(x) { +>fn14 : (x: any) => Promise +>x : any + + var [, x] = y; +> : undefined +>x : any +>y : any +} + +async function fn15(x) { +>fn15 : (x: any) => Promise +>x : any + + var [...x] = y; +>x : any +>y : any +} + +async function fn16(x) { +>fn16 : (x: any) => Promise +>x : any + + var [[x]] = y; +>x : any +>y : any +} + +async function fn17(x) { +>fn17 : (x: any) => Promise +>x : any + + var [[x] = y] = y; +>x : any +>y : any +>y : any +} + +async function fn18({ x }) { +>fn18 : ({ x }: { x: any; }) => Promise +>x : any + + var x; +>x : any +} + +async function fn19([x]) { +>fn19 : ([x]: [any]) => Promise +>x : any + + var x; +>x : any +} + +async function fn20(x) { +>fn20 : (x: any) => Promise +>x : any + { + var x; +>x : any + } +} + +async function fn21(x) { +>fn21 : (x: any) => Promise +>x : any + + if (y) { +>y : any + + var x; +>x : any + } +} + +async function fn22(x) { +>fn22 : (x: any) => Promise +>x : any + + if (y) { +>y : any + } + else { + var x; +>x : any + } +} + +async function fn23(x) { +>fn23 : (x: any) => Promise +>x : any + + try { + var x; +>x : any + } + catch (e) { +>e : any + } +} + +async function fn24(x) { +>fn24 : (x: any) => Promise +>x : any + + try { + + } + catch (e) { +>e : any + + var x; +>x : any + } +} + +async function fn25(x) { +>fn25 : (x: any) => Promise +>x : any + + try { + + } + catch (x) { +>x : any + + var x; +>x : any + } +} + +async function fn26(x) { +>fn26 : (x: any) => Promise +>x : any + + try { + + } + catch ({ x }) { +>x : any + + var x; +>x : any + } +} + +async function fn27(x) { +>fn27 : (x: any) => Promise +>x : any + + try { + } + finally { + var x; +>x : any + } +} + +async function fn28(x) { +>fn28 : (x: any) => Promise +>x : any + + while (y) { +>y : any + + var x; +>x : any + } +} + +async function fn29(x) { +>fn29 : (x: any) => Promise +>x : any + + do { + var x; +>x : any + } + while (y); +>y : any +} + +async function fn30(x) { +>fn30 : (x: any) => Promise +>x : any + + for (var x = y;;) { +>x : any +>y : any + + } +} + +async function fn31(x) { +>fn31 : (x: any) => Promise +>x : any + + for (var { x } = y;;) { +>x : any +>y : any + } +} + +async function fn32(x) { +>fn32 : (x: any) => Promise +>x : any + + for (;;) { + var x; +>x : any + } +} + +async function fn33(x: string) { +>fn33 : (x: string) => Promise +>x : string + + for (var x in y) { +>x : string +>y : any + } +} + +async function fn34(x) { +>fn34 : (x: any) => Promise +>x : any + + for (var z in y) { +>z : string +>y : any + + var x; +>x : any + } +} + +async function fn35(x) { +>fn35 : (x: any) => Promise +>x : any + + for (var x of y) { +>x : any +>y : any + } +} + +async function fn36(x) { +>fn36 : (x: any) => Promise +>x : any + + for (var { x } of y) { +>x : any +>y : any + } +} + +async function fn37(x) { +>fn37 : (x: any) => Promise +>x : any + + for (var z of y) { +>z : any +>y : any + + var x; +>x : any + } +} + +async function fn38(x) { +>fn38 : (x: any) => Promise +>x : any + + switch (y) { +>y : any + + case y: +>y : any + + var x; +>x : any + } +} diff --git a/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts b/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts new file mode 100644 index 00000000000..017be33edb4 --- /dev/null +++ b/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts @@ -0,0 +1,207 @@ +// @target: es2015 +// @noEmitHelpers: true +// https://github.com/Microsoft/TypeScript/issues/20461 +declare const y: any; + +async function fn1(x) { + var x; +} + +async function fn2(x) { + var x, z; +} + +async function fn3(x) { + var z; +} + +async function fn4(x) { + var x = y; +} + +async function fn5(x) { + var { x } = y; +} + +async function fn6(x) { + var { x, z } = y; +} + +async function fn7(x) { + var { x = y } = y; +} + +async function fn8(x) { + var { z: x } = y; +} + +async function fn9(x) { + var { z: { x } } = y; +} + +async function fn10(x) { + var { z: { x } = y } = y; +} + +async function fn11(x) { + var { ...x } = y; +} + +async function fn12(x) { + var [x] = y; +} + +async function fn13(x) { + var [x = y] = y; +} + +async function fn14(x) { + var [, x] = y; +} + +async function fn15(x) { + var [...x] = y; +} + +async function fn16(x) { + var [[x]] = y; +} + +async function fn17(x) { + var [[x] = y] = y; +} + +async function fn18({ x }) { + var x; +} + +async function fn19([x]) { + var x; +} + +async function fn20(x) { + { + var x; + } +} + +async function fn21(x) { + if (y) { + var x; + } +} + +async function fn22(x) { + if (y) { + } + else { + var x; + } +} + +async function fn23(x) { + try { + var x; + } + catch (e) { + } +} + +async function fn24(x) { + try { + + } + catch (e) { + var x; + } +} + +async function fn25(x) { + try { + + } + catch (x) { + var x; + } +} + +async function fn26(x) { + try { + + } + catch ({ x }) { + var x; + } +} + +async function fn27(x) { + try { + } + finally { + var x; + } +} + +async function fn28(x) { + while (y) { + var x; + } +} + +async function fn29(x) { + do { + var x; + } + while (y); +} + +async function fn30(x) { + for (var x = y;;) { + + } +} + +async function fn31(x) { + for (var { x } = y;;) { + } +} + +async function fn32(x) { + for (;;) { + var x; + } +} + +async function fn33(x: string) { + for (var x in y) { + } +} + +async function fn34(x) { + for (var z in y) { + var x; + } +} + +async function fn35(x) { + for (var x of y) { + } +} + +async function fn36(x) { + for (var { x } of y) { + } +} + +async function fn37(x) { + for (var z of y) { + var x; + } +} + +async function fn38(x) { + switch (y) { + case y: + var x; + } +} \ No newline at end of file From 16b13fe4490c54b71e8af205c5e0c0c4191ff005 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 16 Jan 2018 15:17:04 -0800 Subject: [PATCH 094/172] Fix incorrect parenthesization logic for conditional expression branches --- src/compiler/factory.ts | 4 ++- ...rmParenthesizesConditionalSubexpression.js | 10 +++++++ ...enthesizesConditionalSubexpression.symbols | 14 +++++++++ ...arenthesizesConditionalSubexpression.types | 30 +++++++++++++++++++ ...rmParenthesizesConditionalSubexpression.ts | 4 +++ 5 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/transformParenthesizesConditionalSubexpression.js create mode 100644 tests/baselines/reference/transformParenthesizesConditionalSubexpression.symbols create mode 100644 tests/baselines/reference/transformParenthesizesConditionalSubexpression.types create mode 100644 tests/cases/compiler/transformParenthesizesConditionalSubexpression.ts diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 14c8ad91eeb..0b52480e165 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -3954,7 +3954,9 @@ namespace ts { // per ES grammar both 'whenTrue' and 'whenFalse' parts of conditional expression are assignment expressions // so in case when comma expression is introduced as a part of previous transformations // if should be wrapped in parens since comma operator has the lowest precedence - return e.kind === SyntaxKind.BinaryExpression && (e).operatorToken.kind === SyntaxKind.CommaToken + const emittedExpression = skipPartiallyEmittedExpressions(e); + return emittedExpression.kind === SyntaxKind.BinaryExpression && (emittedExpression).operatorToken.kind === SyntaxKind.CommaToken || + emittedExpression.kind === SyntaxKind.CommaListExpression ? createParen(e) : e; } diff --git a/tests/baselines/reference/transformParenthesizesConditionalSubexpression.js b/tests/baselines/reference/transformParenthesizesConditionalSubexpression.js new file mode 100644 index 00000000000..4f102f14ed4 --- /dev/null +++ b/tests/baselines/reference/transformParenthesizesConditionalSubexpression.js @@ -0,0 +1,10 @@ +//// [transformParenthesizesConditionalSubexpression.ts] +var K = 'k' +var a = { p : (true ? { [K] : 'v'} : null) } +var b = { p : (true ? { [K] : 'v'} as any : null) } + +//// [transformParenthesizesConditionalSubexpression.js] +var K = 'k'; +var a = { p: (true ? (_a = {}, _a[K] = 'v', _a) : null) }; +var b = { p: (true ? (_b = {}, _b[K] = 'v', _b) : null) }; +var _a, _b; diff --git a/tests/baselines/reference/transformParenthesizesConditionalSubexpression.symbols b/tests/baselines/reference/transformParenthesizesConditionalSubexpression.symbols new file mode 100644 index 00000000000..d3ae64c40d5 --- /dev/null +++ b/tests/baselines/reference/transformParenthesizesConditionalSubexpression.symbols @@ -0,0 +1,14 @@ +=== tests/cases/compiler/transformParenthesizesConditionalSubexpression.ts === +var K = 'k' +>K : Symbol(K, Decl(transformParenthesizesConditionalSubexpression.ts, 0, 3)) + +var a = { p : (true ? { [K] : 'v'} : null) } +>a : Symbol(a, Decl(transformParenthesizesConditionalSubexpression.ts, 1, 3)) +>p : Symbol(p, Decl(transformParenthesizesConditionalSubexpression.ts, 1, 9)) +>K : Symbol(K, Decl(transformParenthesizesConditionalSubexpression.ts, 0, 3)) + +var b = { p : (true ? { [K] : 'v'} as any : null) } +>b : Symbol(b, Decl(transformParenthesizesConditionalSubexpression.ts, 2, 3)) +>p : Symbol(p, Decl(transformParenthesizesConditionalSubexpression.ts, 2, 9)) +>K : Symbol(K, Decl(transformParenthesizesConditionalSubexpression.ts, 0, 3)) + diff --git a/tests/baselines/reference/transformParenthesizesConditionalSubexpression.types b/tests/baselines/reference/transformParenthesizesConditionalSubexpression.types new file mode 100644 index 00000000000..0ba4268d80a --- /dev/null +++ b/tests/baselines/reference/transformParenthesizesConditionalSubexpression.types @@ -0,0 +1,30 @@ +=== tests/cases/compiler/transformParenthesizesConditionalSubexpression.ts === +var K = 'k' +>K : string +>'k' : "k" + +var a = { p : (true ? { [K] : 'v'} : null) } +>a : { p: { [x: string]: string; }; } +>{ p : (true ? { [K] : 'v'} : null) } : { p: { [x: string]: string; }; } +>p : { [x: string]: string; } +>(true ? { [K] : 'v'} : null) : { [x: string]: string; } +>true ? { [K] : 'v'} : null : { [x: string]: string; } +>true : true +>{ [K] : 'v'} : { [x: string]: string; } +>K : string +>'v' : "v" +>null : null + +var b = { p : (true ? { [K] : 'v'} as any : null) } +>b : { p: any; } +>{ p : (true ? { [K] : 'v'} as any : null) } : { p: any; } +>p : any +>(true ? { [K] : 'v'} as any : null) : any +>true ? { [K] : 'v'} as any : null : any +>true : true +>{ [K] : 'v'} as any : any +>{ [K] : 'v'} : { [x: string]: string; } +>K : string +>'v' : "v" +>null : null + diff --git a/tests/cases/compiler/transformParenthesizesConditionalSubexpression.ts b/tests/cases/compiler/transformParenthesizesConditionalSubexpression.ts new file mode 100644 index 00000000000..d844a910279 --- /dev/null +++ b/tests/cases/compiler/transformParenthesizesConditionalSubexpression.ts @@ -0,0 +1,4 @@ +// @target: es5 +var K = 'k' +var a = { p : (true ? { [K] : 'v'} : null) } +var b = { p : (true ? { [K] : 'v'} as any : null) } \ No newline at end of file From 6c9827725c30f7c4bb2567192db5a69ca35b5490 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 16 Jan 2018 15:45:10 -0800 Subject: [PATCH 095/172] Fix symbol-named properties incorrectly requiring alignment with string indexer type --- src/compiler/checker.ts | 3 +- tests/baselines/reference/symbolProperty60.js | 25 ++++++++++ .../reference/symbolProperty60.symbols | 48 +++++++++++++++++++ .../reference/symbolProperty60.types | 48 +++++++++++++++++++ .../es6/Symbols/symbolProperty60.ts | 23 +++++++++ 5 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/symbolProperty60.js create mode 100644 tests/baselines/reference/symbolProperty60.symbols create mode 100644 tests/baselines/reference/symbolProperty60.types create mode 100644 tests/cases/conformance/es6/Symbols/symbolProperty60.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 18ba4f2f047..489fff16e2c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22298,7 +22298,8 @@ namespace ts { indexType: Type, indexKind: IndexKind): void { - if (!indexType) { + // ESSymbol properties apply to neither string nor numeric indexers. + if (!indexType || isKnownSymbol(prop)) { return; } diff --git a/tests/baselines/reference/symbolProperty60.js b/tests/baselines/reference/symbolProperty60.js new file mode 100644 index 00000000000..d2a1ac92dd8 --- /dev/null +++ b/tests/baselines/reference/symbolProperty60.js @@ -0,0 +1,25 @@ +//// [symbolProperty60.ts] +// https://github.com/Microsoft/TypeScript/issues/20146 +interface I1 { + [Symbol.toStringTag]: string; + [key: string]: number; +} + +interface I2 { + [Symbol.toStringTag]: string; + [key: number]: boolean; +} + +declare const mySymbol: unique symbol; + +interface I3 { + [mySymbol]: string; + [key: string]: number; +} + +interface I4 { + [mySymbol]: string; + [key: number]: boolean; +} + +//// [symbolProperty60.js] diff --git a/tests/baselines/reference/symbolProperty60.symbols b/tests/baselines/reference/symbolProperty60.symbols new file mode 100644 index 00000000000..fca43277e97 --- /dev/null +++ b/tests/baselines/reference/symbolProperty60.symbols @@ -0,0 +1,48 @@ +=== tests/cases/conformance/es6/Symbols/symbolProperty60.ts === +// https://github.com/Microsoft/TypeScript/issues/20146 +interface I1 { +>I1 : Symbol(I1, Decl(symbolProperty60.ts, 0, 0)) + + [Symbol.toStringTag]: string; +>Symbol.toStringTag : Symbol(SymbolConstructor.toStringTag, Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>Symbol : Symbol(Symbol, Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --)) +>toStringTag : Symbol(SymbolConstructor.toStringTag, Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) + + [key: string]: number; +>key : Symbol(key, Decl(symbolProperty60.ts, 3, 5)) +} + +interface I2 { +>I2 : Symbol(I2, Decl(symbolProperty60.ts, 4, 1)) + + [Symbol.toStringTag]: string; +>Symbol.toStringTag : Symbol(SymbolConstructor.toStringTag, Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>Symbol : Symbol(Symbol, Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --)) +>toStringTag : Symbol(SymbolConstructor.toStringTag, Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) + + [key: number]: boolean; +>key : Symbol(key, Decl(symbolProperty60.ts, 8, 5)) +} + +declare const mySymbol: unique symbol; +>mySymbol : Symbol(mySymbol, Decl(symbolProperty60.ts, 11, 13)) + +interface I3 { +>I3 : Symbol(I3, Decl(symbolProperty60.ts, 11, 38)) + + [mySymbol]: string; +>mySymbol : Symbol(mySymbol, Decl(symbolProperty60.ts, 11, 13)) + + [key: string]: number; +>key : Symbol(key, Decl(symbolProperty60.ts, 15, 5)) +} + +interface I4 { +>I4 : Symbol(I4, Decl(symbolProperty60.ts, 16, 1)) + + [mySymbol]: string; +>mySymbol : Symbol(mySymbol, Decl(symbolProperty60.ts, 11, 13)) + + [key: number]: boolean; +>key : Symbol(key, Decl(symbolProperty60.ts, 20, 5)) +} diff --git a/tests/baselines/reference/symbolProperty60.types b/tests/baselines/reference/symbolProperty60.types new file mode 100644 index 00000000000..c81c003bd35 --- /dev/null +++ b/tests/baselines/reference/symbolProperty60.types @@ -0,0 +1,48 @@ +=== tests/cases/conformance/es6/Symbols/symbolProperty60.ts === +// https://github.com/Microsoft/TypeScript/issues/20146 +interface I1 { +>I1 : I1 + + [Symbol.toStringTag]: string; +>Symbol.toStringTag : symbol +>Symbol : SymbolConstructor +>toStringTag : symbol + + [key: string]: number; +>key : string +} + +interface I2 { +>I2 : I2 + + [Symbol.toStringTag]: string; +>Symbol.toStringTag : symbol +>Symbol : SymbolConstructor +>toStringTag : symbol + + [key: number]: boolean; +>key : number +} + +declare const mySymbol: unique symbol; +>mySymbol : unique symbol + +interface I3 { +>I3 : I3 + + [mySymbol]: string; +>mySymbol : unique symbol + + [key: string]: number; +>key : string +} + +interface I4 { +>I4 : I4 + + [mySymbol]: string; +>mySymbol : unique symbol + + [key: number]: boolean; +>key : number +} diff --git a/tests/cases/conformance/es6/Symbols/symbolProperty60.ts b/tests/cases/conformance/es6/Symbols/symbolProperty60.ts new file mode 100644 index 00000000000..4086223f69c --- /dev/null +++ b/tests/cases/conformance/es6/Symbols/symbolProperty60.ts @@ -0,0 +1,23 @@ +// @target: es2015 +// https://github.com/Microsoft/TypeScript/issues/20146 +interface I1 { + [Symbol.toStringTag]: string; + [key: string]: number; +} + +interface I2 { + [Symbol.toStringTag]: string; + [key: number]: boolean; +} + +declare const mySymbol: unique symbol; + +interface I3 { + [mySymbol]: string; + [key: string]: number; +} + +interface I4 { + [mySymbol]: string; + [key: number]: boolean; +} \ No newline at end of file From 4aca0c81218a9623e7629dbc37ef764bf7494dd0 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 16 Jan 2018 16:58:56 -0800 Subject: [PATCH 096/172] Fix destructuring assignment when assignment target is RHS --- src/compiler/transformers/destructuring.ts | 40 ++++++++++++++++++- src/compiler/types.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 +- .../destructuringReassignsRightHandSide.js | 18 +++++++++ ...estructuringReassignsRightHandSide.symbols | 21 ++++++++++ .../destructuringReassignsRightHandSide.types | 27 +++++++++++++ tests/baselines/reference/objectRest.js | 6 +-- .../destructuringReassignsRightHandSide.ts | 9 +++++ 8 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 tests/baselines/reference/destructuringReassignsRightHandSide.js create mode 100644 tests/baselines/reference/destructuringReassignsRightHandSide.symbols create mode 100644 tests/baselines/reference/destructuringReassignsRightHandSide.types create mode 100644 tests/cases/conformance/es6/destructuring/destructuringReassignsRightHandSide.ts diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index a283275d066..bafe43b0b15 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -70,7 +70,13 @@ namespace ts { if (value) { value = visitNode(value, visitor, isExpression); - if (needsValue) { + + if (isIdentifier(value) && bindingOrAssignmentElementAssignsToName(node, value.escapedText)) { + // If the right-hand value of the assignment is also an assignment target then + // we need to cache the right-hand value. + value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ false, location); + } + else if (needsValue) { // If the right-hand value of the destructuring assignment needs to be preserved (as // is the case when the destructuring assignment is part of a larger expression), // then we need to cache the right-hand value. @@ -123,6 +129,27 @@ namespace ts { } } + function bindingOrAssignmentElementAssignsToName(element: BindingOrAssignmentElement, escapedName: __String): boolean { + const target = getTargetOfBindingOrAssignmentElement(element); + if (isBindingOrAssignmentPattern(target)) { + return bindingOrAssignmentPatternAssignsToName(target, escapedName); + } + else if (isIdentifier(target)) { + return target.escapedText === escapedName; + } + return false; + } + + function bindingOrAssignmentPatternAssignsToName(pattern: BindingOrAssignmentPattern, escapedName: __String): boolean { + const elements = getElementsOfBindingOrAssignmentPattern(pattern); + for (const element of elements) { + if (bindingOrAssignmentElementAssignsToName(element, escapedName)) { + return true; + } + } + return false; + } + /** * Flattens a VariableDeclaration or ParameterDeclaration to one or more variable declarations. * @@ -157,6 +184,17 @@ namespace ts { createArrayBindingOrAssignmentElement: makeBindingElement, visitor }; + + if (isVariableDeclaration(node)) { + let initializer = getInitializerOfBindingOrAssignmentElement(node); + if (initializer && isIdentifier(initializer) && bindingOrAssignmentElementAssignsToName(node, initializer.escapedText)) { + // If the right-hand value of the assignment is also an assignment target then + // we need to cache the right-hand value. + initializer = ensureIdentifier(flattenContext, initializer, /*reuseIdentifierExpressions*/ false, initializer); + node = updateVariableDeclaration(node, node.name, node.type, initializer); + } + } + flattenBindingOrAssignmentElement(flattenContext, node, rval, node, skipInitializer); if (pendingExpressions) { const temp = createTempVariable(/*recordTempVariable*/ undefined); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7bac69d4a77..9d970b88197 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1467,7 +1467,7 @@ namespace ts { | SpreadAssignment // AssignmentRestProperty ; - export type BindingOrAssignmentElementTarget = BindingOrAssignmentPattern | Expression; + export type BindingOrAssignmentElementTarget = BindingOrAssignmentPattern | Identifier | PropertyAccessExpression | ElementAccessExpression | OmittedExpression; export type ObjectBindingOrAssignmentPattern = ObjectBindingPattern diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 45ad8135e2b..ce90fef044e 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -877,7 +877,7 @@ declare namespace ts { type DestructuringAssignment = ObjectDestructuringAssignment | ArrayDestructuringAssignment; type BindingOrAssignmentElement = VariableDeclaration | ParameterDeclaration | BindingElement | PropertyAssignment | ShorthandPropertyAssignment | SpreadAssignment | OmittedExpression | SpreadElement | ArrayLiteralExpression | ObjectLiteralExpression | AssignmentExpression | Identifier | PropertyAccessExpression | ElementAccessExpression; type BindingOrAssignmentElementRestIndicator = DotDotDotToken | SpreadElement | SpreadAssignment; - type BindingOrAssignmentElementTarget = BindingOrAssignmentPattern | Expression; + type BindingOrAssignmentElementTarget = BindingOrAssignmentPattern | Identifier | PropertyAccessExpression | ElementAccessExpression | OmittedExpression; type ObjectBindingOrAssignmentPattern = ObjectBindingPattern | ObjectLiteralExpression; type ArrayBindingOrAssignmentPattern = ArrayBindingPattern | ArrayLiteralExpression; type AssignmentPattern = ObjectLiteralExpression | ArrayLiteralExpression; diff --git a/tests/baselines/reference/destructuringReassignsRightHandSide.js b/tests/baselines/reference/destructuringReassignsRightHandSide.js new file mode 100644 index 00000000000..b893202aa1e --- /dev/null +++ b/tests/baselines/reference/destructuringReassignsRightHandSide.js @@ -0,0 +1,18 @@ +//// [destructuringReassignsRightHandSide.ts] +var foo: any = { foo: 1, bar: 2 }; +var bar: any; + +// reassignment in destructuring pattern +({ foo, bar } = foo); + +// reassignment in subsequent var +var { foo, baz } = foo; + +//// [destructuringReassignsRightHandSide.js] +var foo = { foo: 1, bar: 2 }; +var bar; +// reassignment in destructuring pattern +(_a = foo, foo = _a.foo, bar = _a.bar); +// reassignment in subsequent var +var _b = foo, foo = _b.foo, baz = _b.baz; +var _a; diff --git a/tests/baselines/reference/destructuringReassignsRightHandSide.symbols b/tests/baselines/reference/destructuringReassignsRightHandSide.symbols new file mode 100644 index 00000000000..a0b68b7fe53 --- /dev/null +++ b/tests/baselines/reference/destructuringReassignsRightHandSide.symbols @@ -0,0 +1,21 @@ +=== tests/cases/conformance/es6/destructuring/destructuringReassignsRightHandSide.ts === +var foo: any = { foo: 1, bar: 2 }; +>foo : Symbol(foo, Decl(destructuringReassignsRightHandSide.ts, 0, 3), Decl(destructuringReassignsRightHandSide.ts, 7, 5)) +>foo : Symbol(foo, Decl(destructuringReassignsRightHandSide.ts, 0, 16)) +>bar : Symbol(bar, Decl(destructuringReassignsRightHandSide.ts, 0, 24)) + +var bar: any; +>bar : Symbol(bar, Decl(destructuringReassignsRightHandSide.ts, 1, 3)) + +// reassignment in destructuring pattern +({ foo, bar } = foo); +>foo : Symbol(foo, Decl(destructuringReassignsRightHandSide.ts, 4, 2)) +>bar : Symbol(bar, Decl(destructuringReassignsRightHandSide.ts, 4, 7)) +>foo : Symbol(foo, Decl(destructuringReassignsRightHandSide.ts, 0, 3), Decl(destructuringReassignsRightHandSide.ts, 7, 5)) + +// reassignment in subsequent var +var { foo, baz } = foo; +>foo : Symbol(foo, Decl(destructuringReassignsRightHandSide.ts, 0, 3), Decl(destructuringReassignsRightHandSide.ts, 7, 5)) +>baz : Symbol(baz, Decl(destructuringReassignsRightHandSide.ts, 7, 10)) +>foo : Symbol(foo, Decl(destructuringReassignsRightHandSide.ts, 0, 3), Decl(destructuringReassignsRightHandSide.ts, 7, 5)) + diff --git a/tests/baselines/reference/destructuringReassignsRightHandSide.types b/tests/baselines/reference/destructuringReassignsRightHandSide.types new file mode 100644 index 00000000000..f5ba19820e0 --- /dev/null +++ b/tests/baselines/reference/destructuringReassignsRightHandSide.types @@ -0,0 +1,27 @@ +=== tests/cases/conformance/es6/destructuring/destructuringReassignsRightHandSide.ts === +var foo: any = { foo: 1, bar: 2 }; +>foo : any +>{ foo: 1, bar: 2 } : { foo: number; bar: number; } +>foo : number +>1 : 1 +>bar : number +>2 : 2 + +var bar: any; +>bar : any + +// reassignment in destructuring pattern +({ foo, bar } = foo); +>({ foo, bar } = foo) : any +>{ foo, bar } = foo : any +>{ foo, bar } : { foo: any; bar: any; } +>foo : any +>bar : any +>foo : any + +// reassignment in subsequent var +var { foo, baz } = foo; +>foo : any +>baz : any +>foo : any + diff --git a/tests/baselines/reference/objectRest.js b/tests/baselines/reference/objectRest.js index 3576ffb286c..c314377f807 100644 --- a/tests/baselines/reference/objectRest.js +++ b/tests/baselines/reference/objectRest.js @@ -85,10 +85,10 @@ var i = removable; var { removed } = i, removableRest2 = __rest(i, ["removed"]); let computed = 'b'; let computed2 = 'a'; -var _g = computed, stillNotGreat = o[_g], _h = computed2, soSo = o[_h], o = __rest(o, [typeof _g === "symbol" ? _g : _g + "", typeof _h === "symbol" ? _h : _h + ""]); -(_j = computed, stillNotGreat = o[_j], _k = computed2, soSo = o[_k], o = __rest(o, [typeof _j === "symbol" ? _j : _j + "", typeof _k === "symbol" ? _k : _k + ""])); +var _g = o, _h = computed, stillNotGreat = _g[_h], _j = computed2, soSo = _g[_j], o = __rest(_g, [typeof _h === "symbol" ? _h : _h + "", typeof _j === "symbol" ? _j : _j + ""]); +(_k = o, _l = computed, stillNotGreat = _k[_l], _m = computed2, soSo = _k[_m], o = __rest(_k, [typeof _l === "symbol" ? _l : _l + "", typeof _m === "symbol" ? _m : _m + ""])); var noContextualType = (_a) => { var { aNumber = 12 } = _a, notEmptyObject = __rest(_a, ["aNumber"]); return aNumber + notEmptyObject.anythingGoes; }; -var _d, _f, _j, _k; +var _d, _f, _k, _l, _m; diff --git a/tests/cases/conformance/es6/destructuring/destructuringReassignsRightHandSide.ts b/tests/cases/conformance/es6/destructuring/destructuringReassignsRightHandSide.ts new file mode 100644 index 00000000000..042ed5d5248 --- /dev/null +++ b/tests/cases/conformance/es6/destructuring/destructuringReassignsRightHandSide.ts @@ -0,0 +1,9 @@ +// @target: es5 +var foo: any = { foo: 1, bar: 2 }; +var bar: any; + +// reassignment in destructuring pattern +({ foo, bar } = foo); + +// reassignment in subsequent var +var { foo, baz } = foo; \ No newline at end of file From 5320ce25528e27612116d0063c11ed702964bef8 Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Tue, 16 Jan 2018 17:04:14 -0800 Subject: [PATCH 097/172] Revert path normalization in favor of checking for backslash --- src/compiler/core.ts | 2 +- src/compiler/program.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 0faabbda2a0..d92a02661e8 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1909,7 +1909,7 @@ namespace ts { return p2 + 1; } if (path.charCodeAt(1) === CharacterCodes.colon) { - if (path.charCodeAt(2) === CharacterCodes.slash) return 3; + if (path.charCodeAt(2) === CharacterCodes.slash || path.charCodeAt(2) === CharacterCodes.backslash) return 3; } // Per RFC 1738 'file' URI schema has the shape file:/// // if is omitted then it is assumed that host value is 'localhost', diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 8f3cadcddc5..0d1bbf233a3 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -15,8 +15,7 @@ namespace ts { export function resolveTripleslashReference(moduleName: string, containingFile: string): string { const basePath = getDirectoryPath(containingFile); - const normalizedModuleName = normalizePath(moduleName); - const referencedFileName = isRootedDiskPath(normalizedModuleName) ? normalizedModuleName : combinePaths(basePath, normalizedModuleName); + const referencedFileName = isRootedDiskPath(moduleName) ? moduleName : combinePaths(basePath, moduleName); return normalizePath(referencedFileName); } From c4fddba0a9459c3ff0582ae787004368b9f063ef Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 16 Jan 2018 17:11:32 -0800 Subject: [PATCH 098/172] Remove duplicate implementations --- src/compiler/transformers/es2017.ts | 77 ++--------------------------- 1 file changed, 5 insertions(+), 72 deletions(-) diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index 5733d089a7d..f1bd671d461 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -322,7 +322,7 @@ namespace ts { const variables = getInitializedVariables(node); if (variables.length === 0) { if (hasReceiver) { - return convertBindingNameToAssignmentTarget(node.declarations[0].name); + return visitNode(convertToAssignmentElementTarget(node.declarations[0].name), visitor, isExpression); } return undefined; } @@ -348,81 +348,14 @@ namespace ts { } function transformInitializedVariable(node: VariableDeclaration) { - return setSourceMapRange( + const converted = setSourceMapRange( createAssignment( - convertBindingNameToAssignmentTarget(node.name), - visitNode(node.initializer, visitor, isExpression) + convertToAssignmentElementTarget(node.name), + node.initializer ), node ); - } - - function convertBindingNameToAssignmentTarget(node: BindingName): Identifier | AssignmentPattern { - return isObjectBindingPattern(node) ? convertObjectBindingPatternToObjectAssignmentPattern(node) : - isArrayBindingPattern(node) ? convertArrayBindingPatternToArrayAssignmentPattern(node) : - setSourceMapRange(getSynthesizedClone(node), node); - } - - function convertObjectBindingPatternToObjectAssignmentPattern(node: ObjectBindingPattern): ObjectLiteralExpression { - return createObjectLiteral( - map(node.elements, convertObjectBindingElementToObjectAssignmentElement) - ); - } - - function convertObjectBindingElementToObjectAssignmentElement(node: BindingElement): ObjectLiteralElementLike { - if (node.propertyName) { - let expression: Expression = convertBindingNameToAssignmentTarget(node.name); - if (node.initializer) { - expression = createAssignment(expression, visitNode(node.initializer, visitor, isExpression)); - } - return setSourceMapRange( - createPropertyAssignment( - visitNode(node.propertyName, visitor, isPropertyName), - expression - ), - node - ); - } - else if (node.dotDotDotToken) { - return setSourceMapRange( - createSpreadAssignment( - visitNode(cast(node.name, isIdentifier), visitor, isIdentifier) - ), - node - ); - } - else { - return setSourceMapRange( - createShorthandPropertyAssignment( - visitNode(cast(node.name, isIdentifier), visitor, isIdentifier), - visitNode(node.initializer, visitor, isExpression) - ), - node - ); - } - } - - function convertArrayBindingPatternToArrayAssignmentPattern(node: ArrayBindingPattern): ArrayLiteralExpression { - return setSourceMapRange( - createArrayLiteral( - map(node.elements, convertArrayBindingElementToArrayAssignmentElement) - ), - node - ); - } - - function convertArrayBindingElementToArrayAssignmentElement(node: ArrayBindingElement): Expression { - if (isOmittedExpression(node)) return node; - let expression: Expression = convertBindingNameToAssignmentTarget(node.name); - if (node.initializer) { - expression = createAssignment(expression, visitNode(node.initializer, visitor, isExpression)); - setSourceMapRange(expression, node); - } - else if (node.dotDotDotToken) { - expression = createSpread(expression); - setSourceMapRange(expression, node); - } - return expression; + return visitNode(converted, visitor, isExpression); } function collidesWithParameterName({ name }: VariableDeclaration | BindingElement): boolean { From 964565e06968259fc4e6de6f1e88ab5e0663a94a Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Tue, 16 Jan 2018 23:31:46 -0800 Subject: [PATCH 099/172] Update authors for release 2.7 --- .mailmap | 28 ++++++++++++++++++++++++++-- AUTHORS.md | 24 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/.mailmap b/.mailmap index 98f004c58dc..80487998c95 100644 --- a/.mailmap +++ b/.mailmap @@ -140,7 +140,7 @@ Mike Busyrev Mine Starks Mine Starks Mohamed Hegazy ncoley # Natalie Coley -Nathan Shively-Sanders +Nathan Shively-Sanders Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Nathan Yee Nima Zahedi Noah Chen @@ -287,4 +287,28 @@ Stas Vilchik Taras Mankovski Thomas den Hollander Vakhurin Sergey -Zeeshan Ahmed \ No newline at end of file +Zeeshan Ahmed +Orta # Orta Therox +IdeaHunter # @IdeaHunter +kujon # Jakub Korzeniowski +Matt @begincalendar +meyer # @meyer +micbou # @micbou +Alan Agius +Alex Khomchenko +Oussama Ben Brahim benbraou +Cameron Taggart +csigs csigs +Eugene Timokhov +Kris Zyp +Jing Ma +Martin Hiller +Mike Morearty +Priyantha Lankapura <403912+lankaapura@users.noreply.github.com> +Remo H. Jansen +Sean Barag +Sharon Rolel +Stanislav Iliev +Wenlu Wang <805037171@163.com> wenlu.wang <805037171@163.com> kingwl <805037171@163.com> +Wilson Hobbs +Yuval Greenfield \ No newline at end of file diff --git a/AUTHORS.md b/AUTHORS.md index c554a588715..0dfaddb7a25 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -6,8 +6,10 @@ TypeScript is authored by: * Adrian Leonhard * Ahmad Farid * Akshar Patel +* Alan Agius * Alex Chugaev * Alex Eagle +* Alex Khomchenko * Alexander Kuvaev * Alexander Rusakov * Ali Sabzevari @@ -47,6 +49,7 @@ TypeScript is authored by: * Brett Mayen * Bryan Forbes * Caitlin Potter +* Cameron Taggart * @cedvdb * Charles Pierce * Charly POLY @@ -56,6 +59,7 @@ TypeScript is authored by: * Colby Russell * Colin Snover * Cotton Hou +* csigs * Cyrus Najmabadi * Dafrok Zhang * Dahan Gong @@ -85,6 +89,7 @@ TypeScript is authored by: * Erik McClenney * Ethan Resnick * Ethan Rubio +* Eugene Timokhov * Evan Martin * Evan Sebastian * Eyas Sharaiha @@ -111,6 +116,7 @@ TypeScript is authored by: * Herrington Darkholme * Homa Wong * Iain Monro +* @IdeaHunter * Igor Novozhilov * Ika * Ingvar Stepanyan @@ -118,6 +124,7 @@ TypeScript is authored by: * Ivan Enderlin * Ivo Gabe de Wolff * Iwata Hidetaka +* Jakub Korzeniowski * Jakub Młokosiewicz * James Henry * James Whitney @@ -130,6 +137,7 @@ TypeScript is authored by: * Jed Mao * Jeffrey Morlan * Jesse Schalken +* Jing Ma * Jiri Tobisek * Joe Calzaretta * Joe Chung @@ -160,6 +168,7 @@ TypeScript is authored by: * Kevin Lang * Kitson Kelly * Klaus Meinhardt +* Kris Zyp * Kyle Kelley * Kārlis Gaņģis * Lorant Pinter @@ -170,8 +179,10 @@ TypeScript is authored by: * Manish Giri * Marin Marinov * Marius Schulz +* Martin Hiller * Martin Vseticka * Masahiro Wakame +* Matt * Matt Bierner * Matt McCutchen * Matt Mitchell @@ -179,10 +190,13 @@ TypeScript is authored by: * Mattias Buelens * Max Deepfield * Maxwell Paul Brickner +* @meyer * Micah Zoltu +* @micbou * Michael * Michael Bromley * Mike Busyrev +* Mike Morearty * Mine Starks * Mohamed Hegazy * Mohsen Azimi @@ -198,7 +212,9 @@ TypeScript is authored by: * Oleg Mihailik * Oleksandr Chekhovskyi * Omer Sheikh +* Orta Therox * Oskar Segersva¨rd +* Oussama Ben Brahim * Patrick Zhong * Paul Jolly * Paul van Brenk @@ -210,11 +226,13 @@ TypeScript is authored by: * Piero Cangianiello * @piloopin * Prayag Verma +* Priyantha Lankapura * @progre * Punya Biswal * Rado Kirov * Raj Dosanjh * Reiner Dolp +* Remo H. Jansen * Richard Karmazín * Richard Knoll * Richard Sentino @@ -227,8 +245,10 @@ TypeScript is authored by: * Ryohei Ikegami * Sam El-Husseini * Sarangan Rajamanickam +* Sean Barag * Sergey Rubanov * Sergey Shandar +* Sharon Rolel * Sheetal Nandi * Shengping Zhong * Shyyko Serhiy @@ -237,6 +257,7 @@ TypeScript is authored by: * Solal Pirelli * Soo Jae Hwang * Stan Thomas +* Stanislav Iliev * Stanislav Sysoev * Stas Vilchik * Steve Lucco @@ -268,11 +289,14 @@ TypeScript is authored by: * Vilic Vane * Vladimir Kurchatkin * Vladimir Matveev +* Wenlu Wang * Wesley Wigham * William Orr +* Wilson Hobbs * York Yao * @yortus * Yuichi Nukiyama +* Yuval Greenfield * Zeeshan Ahmed * Zev Spitz * Zhengbo Li \ No newline at end of file From be607bd28f3969275f3f10a867b9e21731aaac5b Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 17 Jan 2018 07:33:03 -0800 Subject: [PATCH 100/172] getStringLiteralCompletionEntries: switch on parent.type (#21169) * getStringLiteralCompletionEntries: switch on parent.type * Use a 'default' case and reduce findPrecedingToken calls * fromType -> fromContextualType --- src/services/completions.ts | 148 ++++++++++++++++++++---------------- src/services/utilities.ts | 3 +- 2 files changed, 85 insertions(+), 66 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index ac1eb8f6a46..2afac10a944 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -35,11 +35,14 @@ namespace ts.Completions { return entries && pathCompletionsInfo(entries); } - if (isInString(sourceFile, position)) { - return getStringLiteralCompletionEntries(sourceFile, position, typeChecker, compilerOptions, host, log); + const contextToken = findPrecedingToken(position, sourceFile); + + if (isInString(sourceFile, position, contextToken)) { + return !contextToken || !isStringLiteral(contextToken) && !isNoSubstitutionTemplateLiteral(contextToken) + ? undefined + : getStringLiteralCompletionEntries(sourceFile, contextToken, position, typeChecker, compilerOptions, host, log); } - const contextToken = findPrecedingToken(position, sourceFile); if (contextToken && isBreakOrContinueStatement(contextToken.parent) && (contextToken.kind === SyntaxKind.BreakKeyword || contextToken.kind === SyntaxKind.ContinueKeyword || contextToken.kind === SyntaxKind.Identifier)) { return getLabelCompletionAtPosition(contextToken.parent); @@ -261,70 +264,87 @@ namespace ts.Completions { } } - function getStringLiteralCompletionEntries(sourceFile: SourceFile, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost, log: Log): CompletionInfo | undefined { - const node = findPrecedingToken(position, sourceFile); - if (!node || !isStringLiteral(node) && !isNoSubstitutionTemplateLiteral(node)) { - return undefined; - } + function getStringLiteralCompletionEntries(sourceFile: SourceFile, node: StringLiteralLike, position: number, typeChecker: TypeChecker, compilerOptions: CompilerOptions, host: LanguageServiceHost, log: Log): CompletionInfo | undefined { + switch (node.parent.kind) { + case SyntaxKind.LiteralType: + switch (node.parent.parent.kind) { + case SyntaxKind.TypeReference: + // TODO: GH#21168 + return undefined; + case SyntaxKind.IndexedAccessType: + // Get all apparent property names + // i.e. interface Foo { + // foo: string; + // bar: string; + // } + // let x: Foo["/*completion position*/"] + const type = typeChecker.getTypeFromTypeNode((node.parent.parent as IndexedAccessTypeNode).objectType); + return getStringLiteralCompletionEntriesFromElementAccessOrIndexedAccess(node, sourceFile, type, typeChecker, compilerOptions.target, log); + default: + return undefined; + } - if (node.parent.kind === SyntaxKind.PropertyAssignment && - node.parent.parent.kind === SyntaxKind.ObjectLiteralExpression && - (node.parent).name === node) { - // Get quoted name of properties of the object literal expression - // i.e. interface ConfigFiles { - // 'jspm:dev': string - // } - // let files: ConfigFiles = { - // '/*completion position*/' - // } - // - // function foo(c: ConfigFiles) {} - // foo({ - // '/*completion position*/' - // }); - return getStringLiteralCompletionEntriesFromPropertyAssignment(node.parent, sourceFile, typeChecker, compilerOptions.target, log); - } - else if (isElementAccessExpression(node.parent) && node.parent.argumentExpression === node) { - // Get all names of properties on the expression - // i.e. interface A { - // 'prop1': string - // } - // let a: A; - // a['/*completion position*/'] - const type = typeChecker.getTypeAtLocation(node.parent.expression); - return getStringLiteralCompletionEntriesFromElementAccessOrIndexedAccess(node, sourceFile, type, typeChecker, compilerOptions.target, log); - } - else if (node.parent.kind === SyntaxKind.ImportDeclaration || node.parent.kind === SyntaxKind.ExportDeclaration - || isRequireCall(node.parent, /*checkArgumentIsStringLiteral*/ false) || isImportCall(node.parent) - || isExpressionOfExternalModuleImportEqualsDeclaration(node)) { - // Get all known external module names or complete a path to a module - // i.e. import * as ns from "/*completion position*/"; - // var y = import("/*completion position*/"); - // import x = require("/*completion position*/"); - // var y = require("/*completion position*/"); - // export * from "/*completion position*/"; - const entries = PathCompletions.getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker); - return pathCompletionsInfo(entries); - } - else if (isIndexedAccessTypeNode(node.parent.parent)) { - // Get all apparent property names - // i.e. interface Foo { - // foo: string; - // bar: string; - // } - // let x: Foo["/*completion position*/"] - const type = typeChecker.getTypeFromTypeNode(node.parent.parent.objectType); - return getStringLiteralCompletionEntriesFromElementAccessOrIndexedAccess(node, sourceFile, type, typeChecker, compilerOptions.target, log); - } - else { - const argumentInfo = SignatureHelp.getImmediatelyContainingArgumentInfo(node, position, sourceFile); - if (argumentInfo) { - // Get string literal completions from specialized signatures of the target - // i.e. declare function f(a: 'A'); - // f("/*completion position*/") - return getStringLiteralCompletionEntriesFromCallExpression(argumentInfo, typeChecker); + case SyntaxKind.PropertyAssignment: + if (node.parent.parent.kind === SyntaxKind.ObjectLiteralExpression && + (node.parent).name === node) { + // Get quoted name of properties of the object literal expression + // i.e. interface ConfigFiles { + // 'jspm:dev': string + // } + // let files: ConfigFiles = { + // '/*completion position*/' + // } + // + // function foo(c: ConfigFiles) {} + // foo({ + // '/*completion position*/' + // }); + return getStringLiteralCompletionEntriesFromPropertyAssignment(node.parent, sourceFile, typeChecker, compilerOptions.target, log); + } + return fromContextualType(); + + case SyntaxKind.ElementAccessExpression: { + const { expression, argumentExpression } = node.parent as ElementAccessExpression; + if (node === argumentExpression) { + // Get all names of properties on the expression + // i.e. interface A { + // 'prop1': string + // } + // let a: A; + // a['/*completion position*/'] + const type = typeChecker.getTypeAtLocation(expression); + return getStringLiteralCompletionEntriesFromElementAccessOrIndexedAccess(node, sourceFile, type, typeChecker, compilerOptions.target, log); + } + break; } + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + if (!isRequireCall(node.parent, /*checkArgumentIsStringLiteral*/ false) && !isImportCall(node.parent)) { + const argumentInfo = SignatureHelp.getImmediatelyContainingArgumentInfo(node, position, sourceFile); + // Get string literal completions from specialized signatures of the target + // i.e. declare function f(a: 'A'); + // f("/*completion position*/") + return argumentInfo ? getStringLiteralCompletionEntriesFromCallExpression(argumentInfo, typeChecker) : fromContextualType(); + } + // falls through + + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: + case SyntaxKind.ExternalModuleReference: + // Get all known external module names or complete a path to a module + // i.e. import * as ns from "/*completion position*/"; + // var y = import("/*completion position*/"); + // import x = require("/*completion position*/"); + // var y = require("/*completion position*/"); + // export * from "/*completion position*/"; + return pathCompletionsInfo(PathCompletions.getStringLiteralCompletionsFromModuleNames(sourceFile, node as StringLiteral, compilerOptions, host, typeChecker)); + + default: + return fromContextualType(); + } + + function fromContextualType(): CompletionInfo { // Get completion for string literal from string literal type // i.e. var x: "hi" | "hello" = "/*completion position*/" return getStringLiteralCompletionEntriesFromType(getContextualTypeFromParent(node, typeChecker), typeChecker); diff --git a/src/services/utilities.ts b/src/services/utilities.ts index c5694625493..abfe5a7a55d 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -833,8 +833,7 @@ namespace ts { } } - export function isInString(sourceFile: SourceFile, position: number): boolean { - const previousToken = findPrecedingToken(position, sourceFile); + export function isInString(sourceFile: SourceFile, position: number, previousToken = findPrecedingToken(position, sourceFile)): boolean { if (previousToken && isStringTextContainingNode(previousToken)) { const start = previousToken.getStart(); const end = previousToken.getEnd(); From 2ca688a87d028c7791fdfc0fb3220e11e0e8559d Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 18 Jan 2018 00:42:40 +0700 Subject: [PATCH 101/172] Remove the colon from the message in tsconfig.json (#21174) * Add additional diagnostic message for tsconfig.json * Add a way to override description for tsconfig.json * Modify reference tsconfig.json in tests * Don't have to differentiate cmdline and tsconfig.json description This reverts commit efe57733cac305533c61c7bea3d51b302bbb4d19 and 94f9e67a247f2d73ddff6935083f76812efd2a21. * Remove colon from diagnostic message code 6079 * Remove colon from localized messages code 6079 * Revert "Remove colon from localized messages code 6079" Not changing files on src/loc because they are managed using automated tools. This reverts commit e91f52348e17bf829db2418020c4281203d03e05. --- src/compiler/commandLineParser.ts | 2 +- src/compiler/diagnosticMessages.json | 2 +- .../tsConfig/Default initialized TSConfig/tsconfig.json | 2 +- .../tsconfig.json | 2 +- .../tsconfig.json | 2 +- .../Initialized TSConfig with files options/tsconfig.json | 2 +- .../tsconfig.json | 2 +- .../tsconfig.json | 2 +- .../tsconfig.json | 2 +- .../tsconfig.json | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 4813d4b4300..eb9f1e3cf34 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -151,7 +151,7 @@ namespace ts { }, showInSimplifiedHelpView: true, category: Diagnostics.Basic_Options, - description: Diagnostics.Specify_library_files_to_be_included_in_the_compilation_Colon + description: Diagnostics.Specify_library_files_to_be_included_in_the_compilation }, { name: "allowJs", diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index cf5ccb1397d..785ac6f953b 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3015,7 +3015,7 @@ "category": "Message", "code": 6078 }, - "Specify library files to be included in the compilation: ": { + "Specify library files to be included in the compilation.": { "category": "Message", "code": 6079 }, diff --git a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json index 26cf24cc2e4..cb57cd1366b 100644 --- a/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Default initialized TSConfig/tsconfig.json @@ -3,7 +3,7 @@ /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - // "lib": [], /* Specify library files to be included in the compilation: */ + // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json index 5d7960c8254..6614dd9e48e 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json @@ -3,7 +3,7 @@ /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - // "lib": [], /* Specify library files to be included in the compilation: */ + // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json index 353cb68ea95..8c19ad58bed 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with enum value compiler options/tsconfig.json @@ -3,7 +3,7 @@ /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - // "lib": [], /* Specify library files to be included in the compilation: */ + // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json index 4a28e6f2491..f5e2b163a21 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with files options/tsconfig.json @@ -3,7 +3,7 @@ /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - // "lib": [], /* Specify library files to be included in the compilation: */ + // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json index 46ae199430d..ced3eae17b8 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json @@ -3,7 +3,7 @@ /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - "lib": ["es5","es2015.promise"], /* Specify library files to be included in the compilation: */ + "lib": ["es5","es2015.promise"], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json index 26cf24cc2e4..cb57cd1366b 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json @@ -3,7 +3,7 @@ /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - // "lib": [], /* Specify library files to be included in the compilation: */ + // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json index 069305e2d35..1356e2258f7 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json @@ -3,7 +3,7 @@ /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - "lib": ["es5","es2015.core"], /* Specify library files to be included in the compilation: */ + "lib": ["es5","es2015.core"], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ diff --git a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json index 9e1c409f56e..473280f675e 100644 --- a/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json +++ b/tests/baselines/reference/tsConfig/Initialized TSConfig with list compiler options/tsconfig.json @@ -3,7 +3,7 @@ /* Basic Options */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - // "lib": [], /* Specify library files to be included in the compilation: */ + // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ From 543b48d031f0127e0b57197dcf66e4a02a3c690f Mon Sep 17 00:00:00 2001 From: uniqueiniquity Date: Wed, 17 Jan 2018 10:58:25 -0800 Subject: [PATCH 102/172] Add test for file path in tsconfig --- .../unittests/tsserverProjectSystem.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 565c20ac9cd..68fbc1ecba0 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -2821,6 +2821,31 @@ namespace ts.projectSystem { checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true); }); + it("Properly handle Windows-style outDir", () => { + const configFile: FileOrFolder = { + path: "C:\\a\\tsconfig.json", + content: JSON.stringify({ + compilerOptions: { + outDir: `C:\\a\\b` + }, + include: ["*.ts"] + }) + }; + const file1: FileOrFolder = { + path: "C:\\a\\f1.ts", + content: "let x = 1;" + }; + + const host = createServerHost([file1, configFile], { useWindowsStylePaths: true }); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = configuredProjectAt(projectService, 0); + checkProjectActualFiles(project, [normalizePath(file1.path), normalizePath(configFile.path)]); + const options = project.getCompilerOptions(); + assert.equal(options.outDir, "C:/a/b", ""); + }); }); describe("tsserverProjectSystem Proper errors", () => { From 18a2fc82f7d35528ae31032ade15e2ea07ab3f54 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 17 Jan 2018 11:06:09 -0800 Subject: [PATCH 103/172] Accept tsserverlibrary.d.ts and fix gulpfile --- Gulpfile.ts | 2 +- tests/baselines/reference/api/tsserverlibrary.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gulpfile.ts b/Gulpfile.ts index 2909b10471a..0c410bdfb08 100644 --- a/Gulpfile.ts +++ b/Gulpfile.ts @@ -528,7 +528,7 @@ gulp.task(tsserverLibraryFile, /*help*/ false, [servicesFile, typesMapJson], (do const serverLibraryProject = tsc.createProject("src/server/tsconfig.library.json", getCompilerSettings({ removeComments: false }, /*useBuiltCompiler*/ true)); const {js, dts}: { js: NodeJS.ReadableStream, dts: NodeJS.ReadableStream } = serverLibraryProject.src() .pipe(sourcemaps.init()) - .pipe(newer(tsserverLibraryFile)) + .pipe(newer({ dest: tsserverLibraryFile, extra: ["src/compiler/**/*.ts", "src/services/**/*.ts"] })) .pipe(serverLibraryProject()); return merge2([ diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index e57b52942f4..a0dfa5f5d17 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -877,7 +877,7 @@ declare namespace ts { type DestructuringAssignment = ObjectDestructuringAssignment | ArrayDestructuringAssignment; type BindingOrAssignmentElement = VariableDeclaration | ParameterDeclaration | BindingElement | PropertyAssignment | ShorthandPropertyAssignment | SpreadAssignment | OmittedExpression | SpreadElement | ArrayLiteralExpression | ObjectLiteralExpression | AssignmentExpression | Identifier | PropertyAccessExpression | ElementAccessExpression; type BindingOrAssignmentElementRestIndicator = DotDotDotToken | SpreadElement | SpreadAssignment; - type BindingOrAssignmentElementTarget = BindingOrAssignmentPattern | Expression; + type BindingOrAssignmentElementTarget = BindingOrAssignmentPattern | Identifier | PropertyAccessExpression | ElementAccessExpression | OmittedExpression; type ObjectBindingOrAssignmentPattern = ObjectBindingPattern | ObjectLiteralExpression; type ArrayBindingOrAssignmentPattern = ArrayBindingPattern | ArrayLiteralExpression; type AssignmentPattern = ObjectLiteralExpression | ArrayLiteralExpression; From 61fb845b87f768c9e2a1e700902d04ac938f30b6 Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 17 Jan 2018 11:14:03 -0800 Subject: [PATCH 104/172] Get packageId for relative import within a package (#21130) * Get packageId for relative import within a package * Code review * Rename things and add comments * Improve documentation * Test for scoped packages --- src/compiler/moduleNameResolver.ts | 43 +++++++++++++++++- ...catePackage_relativeImportWithinPackage.js | 45 +++++++++++++++++++ ...ackage_relativeImportWithinPackage.symbols | 44 ++++++++++++++++++ ...age_relativeImportWithinPackage.trace.json | 45 +++++++++++++++++++ ...ePackage_relativeImportWithinPackage.types | 45 +++++++++++++++++++ ...kage_relativeImportWithinPackage_scoped.js | 45 +++++++++++++++++++ ...relativeImportWithinPackage_scoped.symbols | 44 ++++++++++++++++++ ...ativeImportWithinPackage_scoped.trace.json | 45 +++++++++++++++++++ ...e_relativeImportWithinPackage_scoped.types | 45 +++++++++++++++++++ ...catePackage_relativeImportWithinPackage.ts | 38 ++++++++++++++++ ...kage_relativeImportWithinPackage_scoped.ts | 38 ++++++++++++++++ 11 files changed, 476 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.js create mode 100644 tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.symbols create mode 100644 tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.trace.json create mode 100644 tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.types create mode 100644 tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.js create mode 100644 tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.symbols create mode 100644 tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.trace.json create mode 100644 tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.types create mode 100644 tests/cases/compiler/duplicatePackage_relativeImportWithinPackage.ts create mode 100644 tests/cases/compiler/duplicatePackage_relativeImportWithinPackage_scoped.ts diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 018bddd420b..e8639ea3d5f 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -801,7 +801,9 @@ namespace ts { } const resolvedFromFile = loadModuleFromFile(extensions, candidate, failedLookupLocations, onlyRecordFailures, state); if (resolvedFromFile) { - return noPackageId(resolvedFromFile); + const nm = considerPackageJson ? parseNodeModuleFromPath(resolvedFromFile.path) : undefined; + const packageId = nm && getPackageJsonInfo(nm.packageDirectory, nm.subModuleName, failedLookupLocations, /*onlyRecordFailures*/ false, state).packageId; + return withPackageId(packageId, resolvedFromFile); } } if (!onlyRecordFailures) { @@ -816,6 +818,45 @@ namespace ts { return loadNodeModuleFromDirectory(extensions, candidate, failedLookupLocations, onlyRecordFailures, state, considerPackageJson); } + const nodeModulesPathPart = "/node_modules/"; + + /** + * This will be called on the successfully resolved path from `loadModuleFromFile`. + * (Not neeeded for `loadModuleFromNodeModules` as that looks up the `package.json` as part of resolution.) + * + * packageDirectory is the directory of the package itself. + * subModuleName is the path within the package. + * For `blah/node_modules/foo/index.d.ts` this is { packageDirectory: "foo", subModuleName: "" }. (Part before "/node_modules/" is ignored.) + * For `/node_modules/foo/bar.d.ts` this is { packageDirectory: "foo", subModuleName": "bar" }. + * For `/node_modules/@types/foo/bar/index.d.ts` this is { packageDirectory: "@types/foo", subModuleName: "bar" }. + */ + function parseNodeModuleFromPath(path: string): { packageDirectory: string, subModuleName: string } | undefined { + path = normalizePath(path); + const idx = path.lastIndexOf(nodeModulesPathPart); + if (idx === -1) { + return undefined; + } + + const indexAfterNodeModules = idx + nodeModulesPathPart.length; + let indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterNodeModules); + if (path.charCodeAt(indexAfterNodeModules) === CharacterCodes.at) { + indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterPackageName); + } + const packageDirectory = path.slice(0, indexAfterPackageName); + const subModuleName = removeExtensionAndIndex(path.slice(indexAfterPackageName + 1)); + return { packageDirectory, subModuleName }; + } + + function moveToNextDirectorySeparatorIfAvailable(path: string, prevSeparatorIndex: number): number { + const nextSeparatorIndex = path.indexOf(directorySeparator, prevSeparatorIndex + 1); + return nextSeparatorIndex === -1 ? prevSeparatorIndex : nextSeparatorIndex; + } + + function removeExtensionAndIndex(path: string): string { + const noExtension = removeFileExtension(path); + return noExtension === "index" ? "" : removeSuffix(noExtension, "/index"); + } + /* @internal */ export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean }): boolean { // if host does not support 'directoryExists' assume that directory will exist diff --git a/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.js b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.js new file mode 100644 index 00000000000..5236a93f931 --- /dev/null +++ b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.js @@ -0,0 +1,45 @@ +//// [tests/cases/compiler/duplicatePackage_relativeImportWithinPackage.ts] //// + +//// [package.json] +{ + "name": "foo", + "version": "1.2.3" +} + +//// [index.d.ts] +export class C { + private x: number; +} + +//// [index.d.ts] +import { C } from "foo"; +export const o: C; + +//// [use.d.ts] +import { C } from "./index"; +export function use(o: C): void; + +//// [index.d.ts] +export class C { + private x: number; +} + +//// [package.json] +{ + "name": "foo", + "version": "1.2.3" +} + +//// [index.ts] +import { use } from "foo/use"; +import { o } from "a"; + +use(o); + + +//// [index.js] +"use strict"; +exports.__esModule = true; +var use_1 = require("foo/use"); +var a_1 = require("a"); +use_1.use(a_1.o); diff --git a/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.symbols b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.symbols new file mode 100644 index 00000000000..19809675af2 --- /dev/null +++ b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.symbols @@ -0,0 +1,44 @@ +=== /index.ts === +import { use } from "foo/use"; +>use : Symbol(use, Decl(index.ts, 0, 8)) + +import { o } from "a"; +>o : Symbol(o, Decl(index.ts, 1, 8)) + +use(o); +>use : Symbol(use, Decl(index.ts, 0, 8)) +>o : Symbol(o, Decl(index.ts, 1, 8)) + +=== /node_modules/a/node_modules/foo/index.d.ts === +export class C { +>C : Symbol(C, Decl(index.d.ts, 0, 0)) + + private x: number; +>x : Symbol(C.x, Decl(index.d.ts, 0, 16)) +} + +=== /node_modules/a/index.d.ts === +import { C } from "foo"; +>C : Symbol(C, Decl(index.d.ts, 0, 8)) + +export const o: C; +>o : Symbol(o, Decl(index.d.ts, 1, 12)) +>C : Symbol(C, Decl(index.d.ts, 0, 8)) + +=== /node_modules/foo/use.d.ts === +import { C } from "./index"; +>C : Symbol(C, Decl(use.d.ts, 0, 8)) + +export function use(o: C): void; +>use : Symbol(use, Decl(use.d.ts, 0, 28)) +>o : Symbol(o, Decl(use.d.ts, 1, 20)) +>C : Symbol(C, Decl(use.d.ts, 0, 8)) + +=== /node_modules/foo/index.d.ts === +export class C { +>C : Symbol(C, Decl(index.d.ts, 0, 0)) + + private x: number; +>x : Symbol(C.x, Decl(index.d.ts, 0, 16)) +} + diff --git a/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.trace.json b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.trace.json new file mode 100644 index 00000000000..45f70fd8145 --- /dev/null +++ b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.trace.json @@ -0,0 +1,45 @@ +[ + "======== Resolving module 'foo/use' from '/index.ts'. ========", + "Module resolution kind is not specified, using 'NodeJs'.", + "Loading module 'foo/use' from 'node_modules' folder, target file type 'TypeScript'.", + "Found 'package.json' at '/node_modules/foo/package.json'.", + "File '/node_modules/foo/use.ts' does not exist.", + "File '/node_modules/foo/use.tsx' does not exist.", + "File '/node_modules/foo/use.d.ts' exist - use it as a name resolution result.", + "Resolving real path for '/node_modules/foo/use.d.ts', result '/node_modules/foo/use.d.ts'.", + "======== Module name 'foo/use' was successfully resolved to '/node_modules/foo/use.d.ts'. ========", + "======== Resolving module 'a' from '/index.ts'. ========", + "Module resolution kind is not specified, using 'NodeJs'.", + "Loading module 'a' from 'node_modules' folder, target file type 'TypeScript'.", + "File '/node_modules/a/package.json' does not exist.", + "File '/node_modules/a.ts' does not exist.", + "File '/node_modules/a.tsx' does not exist.", + "File '/node_modules/a.d.ts' does not exist.", + "File '/node_modules/a/index.ts' does not exist.", + "File '/node_modules/a/index.tsx' does not exist.", + "File '/node_modules/a/index.d.ts' exist - use it as a name resolution result.", + "Resolving real path for '/node_modules/a/index.d.ts', result '/node_modules/a/index.d.ts'.", + "======== Module name 'a' was successfully resolved to '/node_modules/a/index.d.ts'. ========", + "======== Resolving module './index' from '/node_modules/foo/use.d.ts'. ========", + "Module resolution kind is not specified, using 'NodeJs'.", + "Loading module as file / folder, candidate module location '/node_modules/foo/index', target file type 'TypeScript'.", + "File '/node_modules/foo/index.ts' does not exist.", + "File '/node_modules/foo/index.tsx' does not exist.", + "File '/node_modules/foo/index.d.ts' exist - use it as a name resolution result.", + "Found 'package.json' at '/node_modules/foo/package.json'.", + "======== Module name './index' was successfully resolved to '/node_modules/foo/index.d.ts'. ========", + "======== Resolving module 'foo' from '/node_modules/a/index.d.ts'. ========", + "Module resolution kind is not specified, using 'NodeJs'.", + "Loading module 'foo' from 'node_modules' folder, target file type 'TypeScript'.", + "Found 'package.json' at '/node_modules/a/node_modules/foo/package.json'.", + "File '/node_modules/a/node_modules/foo.ts' does not exist.", + "File '/node_modules/a/node_modules/foo.tsx' does not exist.", + "File '/node_modules/a/node_modules/foo.d.ts' does not exist.", + "'package.json' does not have a 'typings' field.", + "'package.json' does not have a 'types' field.", + "File '/node_modules/a/node_modules/foo/index.ts' does not exist.", + "File '/node_modules/a/node_modules/foo/index.tsx' does not exist.", + "File '/node_modules/a/node_modules/foo/index.d.ts' exist - use it as a name resolution result.", + "Resolving real path for '/node_modules/a/node_modules/foo/index.d.ts', result '/node_modules/a/node_modules/foo/index.d.ts'.", + "======== Module name 'foo' was successfully resolved to '/node_modules/a/node_modules/foo/index.d.ts'. ========" +] \ No newline at end of file diff --git a/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.types b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.types new file mode 100644 index 00000000000..33931774a1b --- /dev/null +++ b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.types @@ -0,0 +1,45 @@ +=== /index.ts === +import { use } from "foo/use"; +>use : (o: C) => void + +import { o } from "a"; +>o : C + +use(o); +>use(o) : void +>use : (o: C) => void +>o : C + +=== /node_modules/a/node_modules/foo/index.d.ts === +export class C { +>C : C + + private x: number; +>x : number +} + +=== /node_modules/a/index.d.ts === +import { C } from "foo"; +>C : typeof C + +export const o: C; +>o : C +>C : C + +=== /node_modules/foo/use.d.ts === +import { C } from "./index"; +>C : typeof C + +export function use(o: C): void; +>use : (o: C) => void +>o : C +>C : C + +=== /node_modules/foo/index.d.ts === +export class C { +>C : C + + private x: number; +>x : number +} + diff --git a/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.js b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.js new file mode 100644 index 00000000000..822c03ca18d --- /dev/null +++ b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.js @@ -0,0 +1,45 @@ +//// [tests/cases/compiler/duplicatePackage_relativeImportWithinPackage_scoped.ts] //// + +//// [package.json] +{ + "name": "@foo/bar", + "version": "1.2.3" +} + +//// [index.d.ts] +export class C { + private x: number; +} + +//// [index.d.ts] +import { C } from "@foo/bar"; +export const o: C; + +//// [use.d.ts] +import { C } from "./index"; +export function use(o: C): void; + +//// [index.d.ts] +export class C { + private x: number; +} + +//// [package.json] +{ + "name": "@foo/bar", + "version": "1.2.3" +} + +//// [index.ts] +import { use } from "@foo/bar/use"; +import { o } from "a"; + +use(o); + + +//// [index.js] +"use strict"; +exports.__esModule = true; +var use_1 = require("@foo/bar/use"); +var a_1 = require("a"); +use_1.use(a_1.o); diff --git a/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.symbols b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.symbols new file mode 100644 index 00000000000..38cd37e023b --- /dev/null +++ b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.symbols @@ -0,0 +1,44 @@ +=== /index.ts === +import { use } from "@foo/bar/use"; +>use : Symbol(use, Decl(index.ts, 0, 8)) + +import { o } from "a"; +>o : Symbol(o, Decl(index.ts, 1, 8)) + +use(o); +>use : Symbol(use, Decl(index.ts, 0, 8)) +>o : Symbol(o, Decl(index.ts, 1, 8)) + +=== /node_modules/a/node_modules/@foo/bar/index.d.ts === +export class C { +>C : Symbol(C, Decl(index.d.ts, 0, 0)) + + private x: number; +>x : Symbol(C.x, Decl(index.d.ts, 0, 16)) +} + +=== /node_modules/a/index.d.ts === +import { C } from "@foo/bar"; +>C : Symbol(C, Decl(index.d.ts, 0, 8)) + +export const o: C; +>o : Symbol(o, Decl(index.d.ts, 1, 12)) +>C : Symbol(C, Decl(index.d.ts, 0, 8)) + +=== /node_modules/@foo/bar/use.d.ts === +import { C } from "./index"; +>C : Symbol(C, Decl(use.d.ts, 0, 8)) + +export function use(o: C): void; +>use : Symbol(use, Decl(use.d.ts, 0, 28)) +>o : Symbol(o, Decl(use.d.ts, 1, 20)) +>C : Symbol(C, Decl(use.d.ts, 0, 8)) + +=== /node_modules/@foo/bar/index.d.ts === +export class C { +>C : Symbol(C, Decl(index.d.ts, 0, 0)) + + private x: number; +>x : Symbol(C.x, Decl(index.d.ts, 0, 16)) +} + diff --git a/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.trace.json b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.trace.json new file mode 100644 index 00000000000..cae4195aa3d --- /dev/null +++ b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.trace.json @@ -0,0 +1,45 @@ +[ + "======== Resolving module '@foo/bar/use' from '/index.ts'. ========", + "Module resolution kind is not specified, using 'NodeJs'.", + "Loading module '@foo/bar/use' from 'node_modules' folder, target file type 'TypeScript'.", + "Found 'package.json' at '/node_modules/@foo/bar/package.json'.", + "File '/node_modules/@foo/bar/use.ts' does not exist.", + "File '/node_modules/@foo/bar/use.tsx' does not exist.", + "File '/node_modules/@foo/bar/use.d.ts' exist - use it as a name resolution result.", + "Resolving real path for '/node_modules/@foo/bar/use.d.ts', result '/node_modules/@foo/bar/use.d.ts'.", + "======== Module name '@foo/bar/use' was successfully resolved to '/node_modules/@foo/bar/use.d.ts'. ========", + "======== Resolving module 'a' from '/index.ts'. ========", + "Module resolution kind is not specified, using 'NodeJs'.", + "Loading module 'a' from 'node_modules' folder, target file type 'TypeScript'.", + "File '/node_modules/a/package.json' does not exist.", + "File '/node_modules/a.ts' does not exist.", + "File '/node_modules/a.tsx' does not exist.", + "File '/node_modules/a.d.ts' does not exist.", + "File '/node_modules/a/index.ts' does not exist.", + "File '/node_modules/a/index.tsx' does not exist.", + "File '/node_modules/a/index.d.ts' exist - use it as a name resolution result.", + "Resolving real path for '/node_modules/a/index.d.ts', result '/node_modules/a/index.d.ts'.", + "======== Module name 'a' was successfully resolved to '/node_modules/a/index.d.ts'. ========", + "======== Resolving module './index' from '/node_modules/@foo/bar/use.d.ts'. ========", + "Module resolution kind is not specified, using 'NodeJs'.", + "Loading module as file / folder, candidate module location '/node_modules/@foo/bar/index', target file type 'TypeScript'.", + "File '/node_modules/@foo/bar/index.ts' does not exist.", + "File '/node_modules/@foo/bar/index.tsx' does not exist.", + "File '/node_modules/@foo/bar/index.d.ts' exist - use it as a name resolution result.", + "Found 'package.json' at '/node_modules/@foo/bar/package.json'.", + "======== Module name './index' was successfully resolved to '/node_modules/@foo/bar/index.d.ts'. ========", + "======== Resolving module '@foo/bar' from '/node_modules/a/index.d.ts'. ========", + "Module resolution kind is not specified, using 'NodeJs'.", + "Loading module '@foo/bar' from 'node_modules' folder, target file type 'TypeScript'.", + "Found 'package.json' at '/node_modules/a/node_modules/@foo/bar/package.json'.", + "File '/node_modules/a/node_modules/@foo/bar.ts' does not exist.", + "File '/node_modules/a/node_modules/@foo/bar.tsx' does not exist.", + "File '/node_modules/a/node_modules/@foo/bar.d.ts' does not exist.", + "'package.json' does not have a 'typings' field.", + "'package.json' does not have a 'types' field.", + "File '/node_modules/a/node_modules/@foo/bar/index.ts' does not exist.", + "File '/node_modules/a/node_modules/@foo/bar/index.tsx' does not exist.", + "File '/node_modules/a/node_modules/@foo/bar/index.d.ts' exist - use it as a name resolution result.", + "Resolving real path for '/node_modules/a/node_modules/@foo/bar/index.d.ts', result '/node_modules/a/node_modules/@foo/bar/index.d.ts'.", + "======== Module name '@foo/bar' was successfully resolved to '/node_modules/a/node_modules/@foo/bar/index.d.ts'. ========" +] \ No newline at end of file diff --git a/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.types b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.types new file mode 100644 index 00000000000..7be2442bf3b --- /dev/null +++ b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.types @@ -0,0 +1,45 @@ +=== /index.ts === +import { use } from "@foo/bar/use"; +>use : (o: C) => void + +import { o } from "a"; +>o : C + +use(o); +>use(o) : void +>use : (o: C) => void +>o : C + +=== /node_modules/a/node_modules/@foo/bar/index.d.ts === +export class C { +>C : C + + private x: number; +>x : number +} + +=== /node_modules/a/index.d.ts === +import { C } from "@foo/bar"; +>C : typeof C + +export const o: C; +>o : C +>C : C + +=== /node_modules/@foo/bar/use.d.ts === +import { C } from "./index"; +>C : typeof C + +export function use(o: C): void; +>use : (o: C) => void +>o : C +>C : C + +=== /node_modules/@foo/bar/index.d.ts === +export class C { +>C : C + + private x: number; +>x : number +} + diff --git a/tests/cases/compiler/duplicatePackage_relativeImportWithinPackage.ts b/tests/cases/compiler/duplicatePackage_relativeImportWithinPackage.ts new file mode 100644 index 00000000000..02ae0c58d02 --- /dev/null +++ b/tests/cases/compiler/duplicatePackage_relativeImportWithinPackage.ts @@ -0,0 +1,38 @@ +// @noImplicitReferences: true +// @traceResolution: true + +// @Filename: /node_modules/a/node_modules/foo/package.json +{ + "name": "foo", + "version": "1.2.3" +} + +// @Filename: /node_modules/a/node_modules/foo/index.d.ts +export class C { + private x: number; +} + +// @Filename: /node_modules/a/index.d.ts +import { C } from "foo"; +export const o: C; + +// @Filename: /node_modules/foo/use.d.ts +import { C } from "./index"; +export function use(o: C): void; + +// @Filename: /node_modules/foo/index.d.ts +export class C { + private x: number; +} + +// @Filename: /node_modules/foo/package.json +{ + "name": "foo", + "version": "1.2.3" +} + +// @Filename: /index.ts +import { use } from "foo/use"; +import { o } from "a"; + +use(o); diff --git a/tests/cases/compiler/duplicatePackage_relativeImportWithinPackage_scoped.ts b/tests/cases/compiler/duplicatePackage_relativeImportWithinPackage_scoped.ts new file mode 100644 index 00000000000..5ccf69a5a8d --- /dev/null +++ b/tests/cases/compiler/duplicatePackage_relativeImportWithinPackage_scoped.ts @@ -0,0 +1,38 @@ +// @noImplicitReferences: true +// @traceResolution: true + +// @Filename: /node_modules/a/node_modules/@foo/bar/package.json +{ + "name": "@foo/bar", + "version": "1.2.3" +} + +// @Filename: /node_modules/a/node_modules/@foo/bar/index.d.ts +export class C { + private x: number; +} + +// @Filename: /node_modules/a/index.d.ts +import { C } from "@foo/bar"; +export const o: C; + +// @Filename: /node_modules/@foo/bar/use.d.ts +import { C } from "./index"; +export function use(o: C): void; + +// @Filename: /node_modules/@foo/bar/index.d.ts +export class C { + private x: number; +} + +// @Filename: /node_modules/@foo/bar/package.json +{ + "name": "@foo/bar", + "version": "1.2.3" +} + +// @Filename: /index.ts +import { use } from "@foo/bar/use"; +import { o } from "a"; + +use(o); From 2ba29d8d9d923d99b828a999b99c47db9cd1184f Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 17 Jan 2018 11:24:28 -0800 Subject: [PATCH 105/172] Support labeled statement --- src/compiler/transformers/es2017.ts | 6 ++-- src/compiler/utilities.ts | 29 +++++++++++++++++++ .../reference/asyncWithVarShadowing_es6.js | 15 ++++++++++ .../asyncWithVarShadowing_es6.symbols | 12 ++++++++ .../reference/asyncWithVarShadowing_es6.types | 15 ++++++++++ .../async/es6/asyncWithVarShadowing_es6.ts | 7 +++++ 6 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index f1bd671d461..79668dc0b8c 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -95,6 +95,8 @@ namespace ts { return visitForInStatementInAsyncBody(node); case SyntaxKind.ForOfStatement: return visitForOfStatementInAsyncBody(node); + case SyntaxKind.CatchClause: + return visitCatchClauseInAsyncBody(node); case SyntaxKind.Block: case SyntaxKind.SwitchStatement: case SyntaxKind.CaseBlock: @@ -105,10 +107,10 @@ namespace ts { case SyntaxKind.WhileStatement: case SyntaxKind.IfStatement: case SyntaxKind.WithStatement: + case SyntaxKind.LabeledStatement: return visitEachChild(node, asyncBodyVisitor, context); - case SyntaxKind.CatchClause: - return visitCatchClauseInAsyncBody(node); } + Debug.assert(!isNodeWithPossibleVarDeclaration(node), "Unhandled node."); return visitor(node); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 796ed35a861..5510353d1da 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1761,6 +1761,35 @@ namespace ts { return getAssignmentTargetKind(node) !== AssignmentKind.None; } + /** + * Indicates whether a node could contain embedded statements that share the same `var` + * declaration scope as its parent. + */ + export function isNodeWithPossibleVarDeclaration(node: Node) { + switch (node.kind) { + case SyntaxKind.SourceFile: + case SyntaxKind.Block: + case SyntaxKind.WithStatement: + case SyntaxKind.IfStatement: + case SyntaxKind.SwitchStatement: + case SyntaxKind.CaseBlock: + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + case SyntaxKind.LabeledStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.TryStatement: + case SyntaxKind.CatchClause: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ModuleBlock: + return true; + } + return false; + } + function walkUp(node: Node, kind: SyntaxKind) { while (node && node.kind === kind) { node = node.parent; diff --git a/tests/baselines/reference/asyncWithVarShadowing_es6.js b/tests/baselines/reference/asyncWithVarShadowing_es6.js index 726bf935cf6..e362a74072d 100644 --- a/tests/baselines/reference/asyncWithVarShadowing_es6.js +++ b/tests/baselines/reference/asyncWithVarShadowing_es6.js @@ -203,6 +203,13 @@ async function fn38(x) { case y: var x; } +} + +async function fn39(x) { + foo: { + var x; + break foo; + } } //// [asyncWithVarShadowing_es6.js] @@ -461,3 +468,11 @@ function fn38(x) { }); var x; } +function fn39(x) { + return __awaiter(this, void 0, void 0, function* () { + foo: { + break foo; + } + }); + var x; +} diff --git a/tests/baselines/reference/asyncWithVarShadowing_es6.symbols b/tests/baselines/reference/asyncWithVarShadowing_es6.symbols index aec43c553a5..f3fd27e8971 100644 --- a/tests/baselines/reference/asyncWithVarShadowing_es6.symbols +++ b/tests/baselines/reference/asyncWithVarShadowing_es6.symbols @@ -404,3 +404,15 @@ async function fn38(x) { >x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 199, 20), Decl(asyncWithVarShadowing_es6.ts, 202, 15)) } } + +async function fn39(x) { +>fn39 : Symbol(fn39, Decl(asyncWithVarShadowing_es6.ts, 204, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 206, 20), Decl(asyncWithVarShadowing_es6.ts, 208, 11)) + + foo: { + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 206, 20), Decl(asyncWithVarShadowing_es6.ts, 208, 11)) + + break foo; + } +} diff --git a/tests/baselines/reference/asyncWithVarShadowing_es6.types b/tests/baselines/reference/asyncWithVarShadowing_es6.types index 42a81f168b0..5544c8be2c6 100644 --- a/tests/baselines/reference/asyncWithVarShadowing_es6.types +++ b/tests/baselines/reference/asyncWithVarShadowing_es6.types @@ -408,3 +408,18 @@ async function fn38(x) { >x : any } } + +async function fn39(x) { +>fn39 : (x: any) => Promise +>x : any + + foo: { +>foo : any + + var x; +>x : any + + break foo; +>foo : any + } +} diff --git a/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts b/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts index 017be33edb4..7c9a9846376 100644 --- a/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts +++ b/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts @@ -204,4 +204,11 @@ async function fn38(x) { case y: var x; } +} + +async function fn39(x) { + foo: { + var x; + break foo; + } } \ No newline at end of file From e655446318dbbdcf2dab1a232d607d5f1f89351c Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 17 Jan 2018 11:25:53 -0800 Subject: [PATCH 106/172] Add test for catch block without variable --- .../reference/asyncWithVarShadowing_es6.js | 21 ++++++++++++++++++- .../asyncWithVarShadowing_es6.symbols | 14 +++++++++++++ .../reference/asyncWithVarShadowing_es6.types | 14 +++++++++++++ .../async/es6/asyncWithVarShadowing_es6.ts | 11 +++++++++- 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/tests/baselines/reference/asyncWithVarShadowing_es6.js b/tests/baselines/reference/asyncWithVarShadowing_es6.js index e362a74072d..392bed9d294 100644 --- a/tests/baselines/reference/asyncWithVarShadowing_es6.js +++ b/tests/baselines/reference/asyncWithVarShadowing_es6.js @@ -210,7 +210,17 @@ async function fn39(x) { var x; break foo; } -} +} + +async function fn40(x) { + try { + + } + catch { + var x; + } +} + //// [asyncWithVarShadowing_es6.js] function fn1(x) { @@ -476,3 +486,12 @@ function fn39(x) { }); var x; } +function fn40(x) { + return __awaiter(this, void 0, void 0, function* () { + try { + } + catch (_a) { + } + }); + var x; +} diff --git a/tests/baselines/reference/asyncWithVarShadowing_es6.symbols b/tests/baselines/reference/asyncWithVarShadowing_es6.symbols index f3fd27e8971..ff95648722f 100644 --- a/tests/baselines/reference/asyncWithVarShadowing_es6.symbols +++ b/tests/baselines/reference/asyncWithVarShadowing_es6.symbols @@ -416,3 +416,17 @@ async function fn39(x) { break foo; } } + +async function fn40(x) { +>fn40 : Symbol(fn40, Decl(asyncWithVarShadowing_es6.ts, 211, 1)) +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 213, 20), Decl(asyncWithVarShadowing_es6.ts, 218, 11)) + + try { + + } + catch { + var x; +>x : Symbol(x, Decl(asyncWithVarShadowing_es6.ts, 213, 20), Decl(asyncWithVarShadowing_es6.ts, 218, 11)) + } +} + diff --git a/tests/baselines/reference/asyncWithVarShadowing_es6.types b/tests/baselines/reference/asyncWithVarShadowing_es6.types index 5544c8be2c6..f87e452b2a1 100644 --- a/tests/baselines/reference/asyncWithVarShadowing_es6.types +++ b/tests/baselines/reference/asyncWithVarShadowing_es6.types @@ -423,3 +423,17 @@ async function fn39(x) { >foo : any } } + +async function fn40(x) { +>fn40 : (x: any) => Promise +>x : any + + try { + + } + catch { + var x; +>x : any + } +} + diff --git a/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts b/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts index 7c9a9846376..9083246dd18 100644 --- a/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts +++ b/tests/cases/conformance/async/es6/asyncWithVarShadowing_es6.ts @@ -211,4 +211,13 @@ async function fn39(x) { var x; break foo; } -} \ No newline at end of file +} + +async function fn40(x) { + try { + + } + catch { + var x; + } +} From c549bb57375ee686477e88fd21182c4b5b5c9e9b Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 17 Jan 2018 11:27:21 -0800 Subject: [PATCH 107/172] Fix bug: getNonNullableType before getting signatures of method (#21212) --- src/compiler/checker.ts | 2 +- tests/cases/fourslash/completionsOptionalMethod.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/completionsOptionalMethod.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 489fff16e2c..b3f06788131 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15815,7 +15815,7 @@ namespace ts { } function isValidMethodAccess(method: Symbol, type: Type) { const propType = getTypeOfFuncClassEnumModule(method); - const signatures = getSignaturesOfType(propType, SignatureKind.Call); + const signatures = getSignaturesOfType(getNonNullableType(propType), SignatureKind.Call); Debug.assert(signatures.length !== 0); return signatures.some(sig => { const thisType = getThisTypeOfSignature(sig); diff --git a/tests/cases/fourslash/completionsOptionalMethod.ts b/tests/cases/fourslash/completionsOptionalMethod.ts new file mode 100644 index 00000000000..72399f177f2 --- /dev/null +++ b/tests/cases/fourslash/completionsOptionalMethod.ts @@ -0,0 +1,8 @@ +/// + +// @strictNullChecks: true + +////declare const x: { m?(): void }; +////x./**/ + +verify.completionsAt("", ["m"]); From afaa13947576c828b418d89fa67301df039d500d Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 17 Jan 2018 11:30:48 -0800 Subject: [PATCH 108/172] Namespaces do not have the same 'var' scope --- src/compiler/utilities.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 5510353d1da..7b80da8f639 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1783,8 +1783,6 @@ namespace ts { case SyntaxKind.WhileStatement: case SyntaxKind.TryStatement: case SyntaxKind.CatchClause: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ModuleBlock: return true; } return false; From b363f4f9cd6ef98f9451ccdcc7321d151195200b Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 17 Jan 2018 11:41:23 -0800 Subject: [PATCH 109/172] Log packageId in --traceResolution (#21233) --- src/compiler/diagnosticMessages.json | 4 ++++ src/compiler/moduleNameResolver.ts | 11 ++++++++--- src/compiler/program.ts | 2 +- src/compiler/utilities.ts | 5 +++++ ...catePackage_relativeImportWithinPackage.trace.json | 6 +++--- ...kage_relativeImportWithinPackage_scoped.trace.json | 6 +++--- ...Resolution_packageJson_yesAtPackageRoot.trace.json | 4 ++-- ...Json_yesAtPackageRoot_fakeScopedPackage.trace.json | 4 ++-- 8 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 785ac6f953b..6a14aab2aee 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3451,6 +3451,10 @@ "category": "Error", "code": 6189 }, + "Found 'package.json' at '{0}'. Package ID is '{1}'.": { + "category": "Message", + "code": 6190 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index e8639ea3d5f..f72876bd5fc 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -960,13 +960,18 @@ namespace ts { const directoryExists = !onlyRecordFailures && directoryProbablyExists(nodeModuleDirectory, host); const packageJsonPath = pathToPackageJson(nodeModuleDirectory); if (directoryExists && host.fileExists(packageJsonPath)) { - if (traceEnabled) { - trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath); - } const packageJsonContent = readJson(packageJsonPath, host); const packageId: PackageId = typeof packageJsonContent.name === "string" && typeof packageJsonContent.version === "string" ? { name: packageJsonContent.name, subModuleName, version: packageJsonContent.version } : undefined; + if (traceEnabled) { + if (packageId) { + trace(host, Diagnostics.Found_package_json_at_0_Package_ID_is_1, packageJsonPath, packageIdToString(packageId)); + } + else { + trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath); + } + } return { found: true, packageJsonContent, packageId }; } else { diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 0d1bbf233a3..37fae7195ab 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1817,7 +1817,7 @@ namespace ts { }, shouldCreateNewSourceFile); if (packageId) { - const packageIdKey = `${packageId.name}/${packageId.subModuleName}@${packageId.version}`; + const packageIdKey = packageIdToString(packageId); const fileFromPackageId = packageIdToSourceFile.get(packageIdKey); if (fileFromPackageId) { // Some other SourceFile already exists with this package name and version. diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 357e5179865..ffcdf42c67e 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -116,6 +116,11 @@ namespace ts { return a === b || a && b && a.name === b.name && a.subModuleName === b.subModuleName && a.version === b.version; } + export function packageIdToString({ name, subModuleName, version }: PackageId): string { + const fullName = subModuleName ? `${name}/${subModuleName}` : name; + return `${fullName}@${version}`; + } + export function typeDirectiveIsEqualTo(oldResolution: ResolvedTypeReferenceDirective, newResolution: ResolvedTypeReferenceDirective): boolean { return oldResolution.resolvedFileName === newResolution.resolvedFileName && oldResolution.primary === newResolution.primary; } diff --git a/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.trace.json b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.trace.json index 45f70fd8145..ae3917e931a 100644 --- a/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.trace.json +++ b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage.trace.json @@ -2,7 +2,7 @@ "======== Resolving module 'foo/use' from '/index.ts'. ========", "Module resolution kind is not specified, using 'NodeJs'.", "Loading module 'foo/use' from 'node_modules' folder, target file type 'TypeScript'.", - "Found 'package.json' at '/node_modules/foo/package.json'.", + "Found 'package.json' at '/node_modules/foo/package.json'. Package ID is 'foo/use@1.2.3'.", "File '/node_modules/foo/use.ts' does not exist.", "File '/node_modules/foo/use.tsx' does not exist.", "File '/node_modules/foo/use.d.ts' exist - use it as a name resolution result.", @@ -26,12 +26,12 @@ "File '/node_modules/foo/index.ts' does not exist.", "File '/node_modules/foo/index.tsx' does not exist.", "File '/node_modules/foo/index.d.ts' exist - use it as a name resolution result.", - "Found 'package.json' at '/node_modules/foo/package.json'.", + "Found 'package.json' at '/node_modules/foo/package.json'. Package ID is 'foo@1.2.3'.", "======== Module name './index' was successfully resolved to '/node_modules/foo/index.d.ts'. ========", "======== Resolving module 'foo' from '/node_modules/a/index.d.ts'. ========", "Module resolution kind is not specified, using 'NodeJs'.", "Loading module 'foo' from 'node_modules' folder, target file type 'TypeScript'.", - "Found 'package.json' at '/node_modules/a/node_modules/foo/package.json'.", + "Found 'package.json' at '/node_modules/a/node_modules/foo/package.json'. Package ID is 'foo@1.2.3'.", "File '/node_modules/a/node_modules/foo.ts' does not exist.", "File '/node_modules/a/node_modules/foo.tsx' does not exist.", "File '/node_modules/a/node_modules/foo.d.ts' does not exist.", diff --git a/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.trace.json b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.trace.json index cae4195aa3d..a753e3dfb9a 100644 --- a/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.trace.json +++ b/tests/baselines/reference/duplicatePackage_relativeImportWithinPackage_scoped.trace.json @@ -2,7 +2,7 @@ "======== Resolving module '@foo/bar/use' from '/index.ts'. ========", "Module resolution kind is not specified, using 'NodeJs'.", "Loading module '@foo/bar/use' from 'node_modules' folder, target file type 'TypeScript'.", - "Found 'package.json' at '/node_modules/@foo/bar/package.json'.", + "Found 'package.json' at '/node_modules/@foo/bar/package.json'. Package ID is '@foo/bar/use@1.2.3'.", "File '/node_modules/@foo/bar/use.ts' does not exist.", "File '/node_modules/@foo/bar/use.tsx' does not exist.", "File '/node_modules/@foo/bar/use.d.ts' exist - use it as a name resolution result.", @@ -26,12 +26,12 @@ "File '/node_modules/@foo/bar/index.ts' does not exist.", "File '/node_modules/@foo/bar/index.tsx' does not exist.", "File '/node_modules/@foo/bar/index.d.ts' exist - use it as a name resolution result.", - "Found 'package.json' at '/node_modules/@foo/bar/package.json'.", + "Found 'package.json' at '/node_modules/@foo/bar/package.json'. Package ID is '@foo/bar@1.2.3'.", "======== Module name './index' was successfully resolved to '/node_modules/@foo/bar/index.d.ts'. ========", "======== Resolving module '@foo/bar' from '/node_modules/a/index.d.ts'. ========", "Module resolution kind is not specified, using 'NodeJs'.", "Loading module '@foo/bar' from 'node_modules' folder, target file type 'TypeScript'.", - "Found 'package.json' at '/node_modules/a/node_modules/@foo/bar/package.json'.", + "Found 'package.json' at '/node_modules/a/node_modules/@foo/bar/package.json'. Package ID is '@foo/bar@1.2.3'.", "File '/node_modules/a/node_modules/@foo/bar.ts' does not exist.", "File '/node_modules/a/node_modules/@foo/bar.tsx' does not exist.", "File '/node_modules/a/node_modules/@foo/bar.d.ts' does not exist.", diff --git a/tests/baselines/reference/moduleResolution_packageJson_yesAtPackageRoot.trace.json b/tests/baselines/reference/moduleResolution_packageJson_yesAtPackageRoot.trace.json index 3a87769891b..e852d58f2b3 100644 --- a/tests/baselines/reference/moduleResolution_packageJson_yesAtPackageRoot.trace.json +++ b/tests/baselines/reference/moduleResolution_packageJson_yesAtPackageRoot.trace.json @@ -3,7 +3,7 @@ "Module resolution kind is not specified, using 'NodeJs'.", "Loading module 'foo/bar' from 'node_modules' folder, target file type 'TypeScript'.", "File '/node_modules/foo/bar/package.json' does not exist.", - "Found 'package.json' at '/node_modules/foo/package.json'.", + "Found 'package.json' at '/node_modules/foo/package.json'. Package ID is 'foo/bar@1.2.3'.", "File '/node_modules/foo/bar.ts' does not exist.", "File '/node_modules/foo/bar.tsx' does not exist.", "File '/node_modules/foo/bar.d.ts' does not exist.", @@ -13,7 +13,7 @@ "Directory '/node_modules/@types' does not exist, skipping all lookups in it.", "Loading module 'foo/bar' from 'node_modules' folder, target file type 'JavaScript'.", "File '/node_modules/foo/bar/package.json' does not exist.", - "Found 'package.json' at '/node_modules/foo/package.json'.", + "Found 'package.json' at '/node_modules/foo/package.json'. Package ID is 'foo/bar@1.2.3'.", "File '/node_modules/foo/bar.js' does not exist.", "File '/node_modules/foo/bar.jsx' does not exist.", "File '/node_modules/foo/bar/index.js' exist - use it as a name resolution result.", diff --git a/tests/baselines/reference/moduleResolution_packageJson_yesAtPackageRoot_fakeScopedPackage.trace.json b/tests/baselines/reference/moduleResolution_packageJson_yesAtPackageRoot_fakeScopedPackage.trace.json index c28189a1f7d..19fe1f125bc 100644 --- a/tests/baselines/reference/moduleResolution_packageJson_yesAtPackageRoot_fakeScopedPackage.trace.json +++ b/tests/baselines/reference/moduleResolution_packageJson_yesAtPackageRoot_fakeScopedPackage.trace.json @@ -3,7 +3,7 @@ "Module resolution kind is not specified, using 'NodeJs'.", "Loading module 'foo/@bar' from 'node_modules' folder, target file type 'TypeScript'.", "File '/node_modules/foo/@bar/package.json' does not exist.", - "Found 'package.json' at '/node_modules/foo/package.json'.", + "Found 'package.json' at '/node_modules/foo/package.json'. Package ID is 'foo/@bar@1.2.3'.", "File '/node_modules/foo/@bar.ts' does not exist.", "File '/node_modules/foo/@bar.tsx' does not exist.", "File '/node_modules/foo/@bar.d.ts' does not exist.", @@ -13,7 +13,7 @@ "Directory '/node_modules/@types' does not exist, skipping all lookups in it.", "Loading module 'foo/@bar' from 'node_modules' folder, target file type 'JavaScript'.", "File '/node_modules/foo/@bar/package.json' does not exist.", - "Found 'package.json' at '/node_modules/foo/package.json'.", + "Found 'package.json' at '/node_modules/foo/package.json'. Package ID is 'foo/@bar@1.2.3'.", "File '/node_modules/foo/@bar.js' does not exist.", "File '/node_modules/foo/@bar.jsx' does not exist.", "File '/node_modules/foo/@bar/index.js' exist - use it as a name resolution result.", From 5b45db790729508553016572a5cddb8e806bc214 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 17 Jan 2018 11:55:43 -0800 Subject: [PATCH 110/172] PR Feedback --- src/compiler/transformers/es2017.ts | 51 +++++++++++++++-------------- src/compiler/utilities.ts | 26 ++++++++++++--- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index 79668dc0b8c..905686c8861 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -86,31 +86,34 @@ namespace ts { } function asyncBodyVisitor(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.VariableStatement: - return visitVariableStatementInAsyncBody(node); - case SyntaxKind.ForStatement: - return visitForStatementInAsyncBody(node); - case SyntaxKind.ForInStatement: - return visitForInStatementInAsyncBody(node); - case SyntaxKind.ForOfStatement: - return visitForOfStatementInAsyncBody(node); - case SyntaxKind.CatchClause: - return visitCatchClauseInAsyncBody(node); - case SyntaxKind.Block: - case SyntaxKind.SwitchStatement: - case SyntaxKind.CaseBlock: - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - case SyntaxKind.TryStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.IfStatement: - case SyntaxKind.WithStatement: - case SyntaxKind.LabeledStatement: - return visitEachChild(node, asyncBodyVisitor, context); + if (isNodeWithPossibleHoistedDeclaration(node)) { + switch (node.kind) { + case SyntaxKind.VariableStatement: + return visitVariableStatementInAsyncBody(node); + case SyntaxKind.ForStatement: + return visitForStatementInAsyncBody(node); + case SyntaxKind.ForInStatement: + return visitForInStatementInAsyncBody(node); + case SyntaxKind.ForOfStatement: + return visitForOfStatementInAsyncBody(node); + case SyntaxKind.CatchClause: + return visitCatchClauseInAsyncBody(node); + case SyntaxKind.Block: + case SyntaxKind.SwitchStatement: + case SyntaxKind.CaseBlock: + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + case SyntaxKind.TryStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.IfStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.LabeledStatement: + return visitEachChild(node, asyncBodyVisitor, context); + default: + return Debug.assertNever(node, "Unhandled node."); + } } - Debug.assert(!isNodeWithPossibleVarDeclaration(node), "Unhandled node."); return visitor(node); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 7b80da8f639..eabc9921c1a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1761,14 +1761,32 @@ namespace ts { return getAssignmentTargetKind(node) !== AssignmentKind.None; } + export type NodeWithPossibleHoistedDeclaration = + | Block + | VariableStatement + | WithStatement + | IfStatement + | SwitchStatement + | CaseBlock + | CaseClause + | DefaultClause + | LabeledStatement + | ForStatement + | ForInStatement + | ForOfStatement + | DoStatement + | WhileStatement + | TryStatement + | CatchClause; + /** - * Indicates whether a node could contain embedded statements that share the same `var` - * declaration scope as its parent. + * Indicates whether a node could contain a `var` VariableDeclarationList that contributes to + * the same `var` declaration scope as the node's parent. */ - export function isNodeWithPossibleVarDeclaration(node: Node) { + export function isNodeWithPossibleHoistedDeclaration(node: Node): node is NodeWithPossibleHoistedDeclaration { switch (node.kind) { - case SyntaxKind.SourceFile: case SyntaxKind.Block: + case SyntaxKind.VariableStatement: case SyntaxKind.WithStatement: case SyntaxKind.IfStatement: case SyntaxKind.SwitchStatement: From 33fd30250d19ca087f191164c424e99d653f88cf Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 17 Jan 2018 11:58:38 -0800 Subject: [PATCH 111/172] update authors --- .mailmap | 3 ++- AUTHORS.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 80487998c95..b246515e467 100644 --- a/.mailmap +++ b/.mailmap @@ -311,4 +311,5 @@ Sharon Rolel Stanislav Iliev Wenlu Wang <805037171@163.com> wenlu.wang <805037171@163.com> kingwl <805037171@163.com> Wilson Hobbs -Yuval Greenfield \ No newline at end of file +Yuval Greenfield +Daniel # @nieltg \ No newline at end of file diff --git a/AUTHORS.md b/AUTHORS.md index 0dfaddb7a25..7662a5642e7 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -205,6 +205,7 @@ TypeScript is authored by: * Nathan Shively-Sanders * Nathan Yee * Nicolas Henry +* @nieltg * Nima Zahedi * Noah Chen * Noel Varanda From 48ac3019b47185b57468384d18e8f73a19fc594d Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 17 Jan 2018 11:59:01 -0800 Subject: [PATCH 112/172] Add example to command description --- scripts/authors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/authors.ts b/scripts/authors.ts index fd9d27acd00..b616ceb8601 100644 --- a/scripts/authors.ts +++ b/scripts/authors.ts @@ -164,7 +164,7 @@ namespace Commands { } }); }; - listAuthors.description = "List known and unknown authors for a given spec"; + listAuthors.description = "List known and unknown authors for a given spec, e.g. 'node authors.js listAuthors origin/release-2.6..origin/release-2.7'"; } var args = process.argv.slice(2); From 8ed885db3e462ec5ddeebfd136945e9a9cc0ac8a Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 17 Jan 2018 12:05:31 -0800 Subject: [PATCH 113/172] Add completions from the 'this' type (#21231) * Add completions from the 'this' type * Code review --- src/compiler/checker.ts | 20 +++++--- src/compiler/types.ts | 2 + src/harness/fourslash.ts | 4 +- src/services/completions.ts | 51 +++++++++++++------ .../cases/fourslash/completionListInScope.ts | 4 +- tests/cases/fourslash/completionsThisType.ts | 29 +++++++++++ tests/cases/fourslash/fourslash.ts | 8 ++- 7 files changed, 93 insertions(+), 25 deletions(-) create mode 100644 tests/cases/fourslash/completionsThisType.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b3f06788131..39679675a31 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -295,6 +295,10 @@ namespace ts { getAccessibleSymbolChain, getTypePredicateOfSignature, resolveExternalModuleSymbol, + tryGetThisTypeAt: node => { + node = getParseTreeNode(node); + return node && tryGetThisTypeAt(node); + }, }; const tupleTypes: GenericType[] = []; @@ -13268,6 +13272,16 @@ namespace ts { if (needToCaptureLexicalThis) { captureLexicalThis(node, container); } + + const type = tryGetThisTypeAt(node, container); + if (!type && noImplicitThis) { + // With noImplicitThis, functions may not reference 'this' if it has type 'any' + error(node, Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation); + } + return type || anyType; + } + + function tryGetThisTypeAt(node: Node, container = getThisContainer(node, /*includeArrowFunctions*/ false)): Type | undefined { if (isFunctionLike(container) && (!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) { // Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated. @@ -13306,12 +13320,6 @@ namespace ts { return type; } } - - if (noImplicitThis) { - // With noImplicitThis, functions may not reference 'this' if it has type 'any' - error(node, Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation); - } - return anyType; } function getTypeForThisExpressionFromJSDoc(node: Node) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 9d970b88197..703b04699b8 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2921,6 +2921,8 @@ namespace ts { /* @internal */ getAccessibleSymbolChain(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean): Symbol[] | undefined; /* @internal */ getTypePredicateOfSignature(signature: Signature): TypePredicate; /* @internal */ resolveExternalModuleSymbol(symbol: Symbol): Symbol; + /** @param node A location where we might consider accessing `this`. Not necessarily a ThisExpression. */ + /* @internal */ tryGetThisTypeAt(node: Node): Type | undefined; } /* @internal */ diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 4c0c72a200f..84bf3bb1752 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -3152,8 +3152,9 @@ Actual: ${stringify(fullActual)}`); assert.isTrue(TestState.textSpansEqual(span, item.replacementSpan), this.assertionMessageAtLastKnownMarker(stringify(span) + " does not equal " + stringify(item.replacementSpan) + " replacement span for " + entryId)); } - assert.equal(item.hasAction, hasAction); + assert.equal(item.hasAction, hasAction, "hasAction"); assert.equal(item.isRecommended, options && options.isRecommended, "isRecommended"); + assert.equal(item.insertText, options && options.insertText, "insertText"); } private findFile(indexOrName: string | number) { @@ -4615,6 +4616,7 @@ namespace FourSlashInterface { export interface VerifyCompletionListContainsOptions extends ts.GetCompletionsAtPositionOptions { sourceDisplay: string; isRecommended?: true; + insertText?: string; } export interface NewContentOptions { diff --git a/src/services/completions.ts b/src/services/completions.ts index 2afac10a944..36a10a1b578 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -4,7 +4,9 @@ namespace ts.Completions { export type Log = (message: string) => void; - interface SymbolOriginInfo { + type SymbolOriginInfo = { type: "this-type" } | SymbolOriginInfoExport; + interface SymbolOriginInfoExport { + type: "export"; moduleSymbol: Symbol; isDefaultExport: boolean; } @@ -170,11 +172,21 @@ namespace ts.Completions { return undefined; } const { name, needsConvertPropertyAccess } = info; - Debug.assert(!(needsConvertPropertyAccess && !propertyAccessToConvert)); if (needsConvertPropertyAccess && !includeInsertTextCompletions) { return undefined; } + let insertText: string | undefined; + let replacementSpan: TextSpan | undefined; + if (kind === CompletionKind.Global && origin && origin.type === "this-type") { + insertText = needsConvertPropertyAccess ? `this["${name}"]` : `this.${name}`; + } + else if (needsConvertPropertyAccess) { + // TODO: GH#20619 Use configured quote style + insertText = `["${name}"]`; + replacementSpan = createTextSpanFromBounds(findChildOfKind(propertyAccessToConvert!, SyntaxKind.DotToken, sourceFile)!.getStart(sourceFile), propertyAccessToConvert!.name.end); + } + // TODO(drosen): Right now we just permit *all* semantic meanings when calling // 'getSymbolKind' which is permissible given that it is backwards compatible; but // really we should consider passing the meaning for the node so that we don't report @@ -189,13 +201,10 @@ namespace ts.Completions { kindModifiers: SymbolDisplay.getSymbolModifiers(symbol), sortText: "0", source: getSourceFromOrigin(origin), - // TODO: GH#20619 Use configured quote style - insertText: needsConvertPropertyAccess ? `["${name}"]` : undefined, - replacementSpan: needsConvertPropertyAccess - ? createTextSpanFromBounds(findChildOfKind(propertyAccessToConvert, SyntaxKind.DotToken, sourceFile)!.getStart(sourceFile), propertyAccessToConvert.name.end) - : undefined, - hasAction: trueOrUndefined(needsConvertPropertyAccess || origin !== undefined), + hasAction: trueOrUndefined(!!origin && origin.type === "export"), isRecommended: trueOrUndefined(isRecommendedCompletionMatch(symbol, recommendedCompletion, typeChecker)), + insertText, + replacementSpan, }; } @@ -210,7 +219,7 @@ namespace ts.Completions { } function getSourceFromOrigin(origin: SymbolOriginInfo | undefined): string | undefined { - return origin && stripQuotes(origin.moduleSymbol.name); + return origin && origin.type === "export" ? stripQuotes(origin.moduleSymbol.name) : undefined; } function getCompletionEntriesFromSymbols( @@ -504,7 +513,7 @@ namespace ts.Completions { } function getSymbolName(symbol: Symbol, origin: SymbolOriginInfo | undefined, target: ScriptTarget): string { - return origin && origin.isDefaultExport && symbol.escapedName === InternalSymbolName.Default + return origin && origin.type === "export" && origin.isDefaultExport && symbol.escapedName === InternalSymbolName.Default // Name of "export default foo;" is "foo". Name of "export default 0" is the filename converted to camelCase. ? firstDefined(symbol.declarations, d => isExportAssignment(d) && isIdentifier(d.expression) ? d.expression.text : undefined) || codefix.moduleSymbolToValidIdentifier(origin.moduleSymbol, target) @@ -590,13 +599,13 @@ namespace ts.Completions { allSourceFiles: ReadonlyArray, ): CodeActionsAndSourceDisplay { const symbolOriginInfo = symbolToOriginInfoMap[getSymbolId(symbol)]; - return symbolOriginInfo + return symbolOriginInfo && symbolOriginInfo.type === "export" ? getCodeActionsAndSourceDisplayForImport(symbolOriginInfo, symbol, program, checker, host, compilerOptions, sourceFile, previousToken, formatContext, getCanonicalFileName, allSourceFiles) : { codeActions: undefined, sourceDisplay: undefined }; } function getCodeActionsAndSourceDisplayForImport( - symbolOriginInfo: SymbolOriginInfo, + symbolOriginInfo: SymbolOriginInfoExport, symbol: Symbol, program: Program, checker: TypeChecker, @@ -1117,6 +1126,18 @@ namespace ts.Completions { const symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace | SymbolFlags.Alias; symbols = typeChecker.getSymbolsInScope(scopeNode, symbolMeanings); + + // Need to insert 'this.' before properties of `this` type, so only do that if `includeInsertTextCompletions` + if (options.includeInsertTextCompletions && scopeNode.kind !== SyntaxKind.SourceFile) { + const thisType = typeChecker.tryGetThisTypeAt(scopeNode); + if (thisType) { + for (const symbol of getPropertiesForCompletion(thisType, typeChecker, /*isForAccess*/ true)) { + symbolToOriginInfoMap[getSymbolId(symbol)] = { type: "this-type" }; + symbols.push(symbol); + } + } + } + if (options.includeExternalModuleExports) { getSymbolsFromOtherSourceFileExports(symbols, previousToken && isIdentifier(previousToken) ? previousToken.text : "", target); } @@ -1230,10 +1251,10 @@ namespace ts.Completions { symbol = getLocalSymbolForExportDefault(symbol) || symbol; } - const origin: SymbolOriginInfo = { moduleSymbol, isDefaultExport }; + const origin: SymbolOriginInfo = { type: "export", moduleSymbol, isDefaultExport }; if (stringContainsCharactersInOrder(getSymbolName(symbol, origin, target).toLowerCase(), tokenTextLowerCase)) { symbols.push(symbol); - symbolToOriginInfoMap[getSymbolId(symbol)] = { moduleSymbol, isDefaultExport }; + symbolToOriginInfoMap[getSymbolId(symbol)] = origin; } } }); @@ -2072,13 +2093,13 @@ namespace ts.Completions { if (isIdentifierText(name, target)) return validIdentiferResult; switch (kind) { case CompletionKind.None: - case CompletionKind.Global: case CompletionKind.MemberLike: return undefined; case CompletionKind.ObjectPropertyDeclaration: // TODO: GH#18169 return { name: JSON.stringify(name), needsConvertPropertyAccess: false }; case CompletionKind.PropertyAccess: + case CompletionKind.Global: // Don't add a completion for a name starting with a space. See https://github.com/Microsoft/TypeScript/pull/20547 return name.charCodeAt(0) === CharacterCodes.space ? undefined : { name, needsConvertPropertyAccess: true }; case CompletionKind.String: diff --git a/tests/cases/fourslash/completionListInScope.ts b/tests/cases/fourslash/completionListInScope.ts index 8773f6d4588..70f82b80f5f 100644 --- a/tests/cases/fourslash/completionListInScope.ts +++ b/tests/cases/fourslash/completionListInScope.ts @@ -13,7 +13,7 @@ //// interface localInterface {} //// export interface exportedInterface {} //// -//// module localModule { +//// module localModule { //// export var x = 0; //// } //// export module exportedModule { @@ -38,7 +38,7 @@ //// interface localInterface2 {} //// export interface exportedInterface2 {} //// -//// module localModule2 { +//// module localModule2 { //// export var x = 0; //// } //// export module exportedModule2 { diff --git a/tests/cases/fourslash/completionsThisType.ts b/tests/cases/fourslash/completionsThisType.ts new file mode 100644 index 00000000000..58325182a22 --- /dev/null +++ b/tests/cases/fourslash/completionsThisType.ts @@ -0,0 +1,29 @@ +/// + +////class C { +//// "foo bar": number; +//// xyz() { +//// /**/ +//// } +////} +//// +////function f(this: { x: number }) { /*f*/ } + +goTo.marker(""); + +verify.completionListContains("xyz", "(method) C.xyz(): void", "", "method", undefined, undefined, { + includeInsertTextCompletions: true, + insertText: "this.xyz", +}); + +verify.completionListContains("foo bar", '(property) C["foo bar"]: number', "", "property", undefined, undefined, { + includeInsertTextCompletions: true, + insertText: 'this["foo bar"]', +}); + +goTo.marker("f"); + +verify.completionListContains("x", "(property) x: number", "", "property", undefined, undefined, { + includeInsertTextCompletions: true, + insertText: "this.x", +}); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 97379957cbf..0bf62eaef92 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -151,7 +151,13 @@ declare namespace FourSlashInterface { kind?: string | { kind?: string, kindModifiers?: string }, spanIndex?: number, hasAction?: boolean, - options?: { includeExternalModuleExports?: boolean, sourceDisplay?: string, isRecommended?: true }, + options?: { + includeExternalModuleExports?: boolean, + includeInsertTextCompletions?: boolean, + sourceDisplay?: string, + isRecommended?: true, + insertText?: string, + }, ): void; completionListItemsCountIsGreaterThan(count: number): void; completionListIsEmpty(): void; From f96dc84a708937df865f84658fde4c011cd3bacf Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 17 Jan 2018 12:42:31 -0800 Subject: [PATCH 114/172] Make getCombinedCodeFix API public (#21234) --- src/server/client.ts | 3 +- src/server/protocol.ts | 16 +------ src/services/types.ts | 11 +---- .../reference/api/tsserverlibrary.d.ts | 47 ++++++++++++++++++- tests/baselines/reference/api/typescript.d.ts | 18 ++++++- 5 files changed, 65 insertions(+), 30 deletions(-) diff --git a/src/server/client.ts b/src/server/client.ts index 7a64d9b528c..8203475b06d 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -558,8 +558,7 @@ namespace ts.server { const request = this.processRequest(CommandNames.GetCodeFixes, args); const response = this.processResponse(request); - // TODO: GH#20538 shouldn't need cast - return (response.body as ReadonlyArray).map(({ description, changes, fixId }) => ({ description, changes: this.convertChanges(changes, file), fixId })); + return response.body.map(({ description, changes, fixId }) => ({ description, changes: this.convertChanges(changes, file), fixId })); } getCombinedCodeFix = notImplemented; diff --git a/src/server/protocol.ts b/src/server/protocol.ts index b3cbba27a20..b4ff0aa4037 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -102,8 +102,6 @@ namespace ts.server.protocol { GetCodeFixes = "getCodeFixes", /* @internal */ GetCodeFixesFull = "getCodeFixes-full", - // TODO: GH#20538 - /* @internal */ GetCombinedCodeFix = "getCombinedCodeFix", /* @internal */ GetCombinedCodeFixFull = "getCombinedCodeFix-full", @@ -557,15 +555,11 @@ namespace ts.server.protocol { arguments: CodeFixRequestArgs; } - // TODO: GH#20538 - /* @internal */ export interface GetCombinedCodeFixRequest extends Request { command: CommandTypes.GetCombinedCodeFix; arguments: GetCombinedCodeFixRequestArgs; } - // TODO: GH#20538 - /* @internal */ export interface GetCombinedCodeFixResponse extends Response { body: CombinedCodeActions; } @@ -622,15 +616,11 @@ namespace ts.server.protocol { errorCodes?: ReadonlyArray; } - // TODO: GH#20538 - /* @internal */ export interface GetCombinedCodeFixRequestArgs { scope: GetCombinedCodeFixScope; fixId: {}; } - // TODO: GH#20538 - /* @internal */ export interface GetCombinedCodeFixScope { type: "file"; args: FileRequestArgs; @@ -1619,7 +1609,7 @@ namespace ts.server.protocol { export interface CodeFixResponse extends Response { /** The code actions that are available */ - body?: CodeAction[]; // TODO: GH#20538 CodeFixAction[] + body?: CodeFixAction[]; } export interface CodeAction { @@ -1631,15 +1621,11 @@ namespace ts.server.protocol { commands?: {}[]; } - // TODO: GH#20538 - /* @internal */ export interface CombinedCodeActions { changes: ReadonlyArray; commands?: ReadonlyArray<{}>; } - // TODO: GH#20538 - /* @internal */ export interface CodeFixAction extends CodeAction { /** * If present, one may call 'getCombinedCodeFix' with this fixId. diff --git a/src/services/types.ts b/src/services/types.ts index 597709a7c79..4ebd4a3e76a 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -295,10 +295,7 @@ namespace ts { getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - // TODO: GH#20538 return `ReadonlyArray` - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray; - // TODO: GH#20538 - /* @internal */ + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray; getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; @@ -327,8 +324,6 @@ namespace ts { dispose(): void; } - // TODO: GH#20538 - /* @internal */ export interface CombinedCodeFixScope { type: "file"; fileName: string; } export interface GetCompletionsAtPositionOptions { @@ -419,8 +414,6 @@ namespace ts { commands?: CodeActionCommand[]; } - // TODO: GH#20538 - /* @internal */ export interface CodeFixAction extends CodeAction { /** * If present, one may call 'getCombinedCodeFix' with this fixId. @@ -429,8 +422,6 @@ namespace ts { fixId?: {}; } - // TODO: GH#20538 - /* @internal */ export interface CombinedCodeActions { changes: ReadonlyArray; commands: ReadonlyArray | undefined; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index a0dfa5f5d17..942ca44a782 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4091,7 +4091,8 @@ declare namespace ts { getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray; + getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -4107,6 +4108,10 @@ declare namespace ts { getProgram(): Program; dispose(): void; } + interface CombinedCodeFixScope { + type: "file"; + fileName: string; + } interface GetCompletionsAtPositionOptions { includeExternalModuleExports: boolean; includeInsertTextCompletions: boolean; @@ -4184,6 +4189,17 @@ declare namespace ts { */ commands?: CodeActionCommand[]; } + interface CodeFixAction extends CodeAction { + /** + * If present, one may call 'getCombinedCodeFix' with this fixId. + * This may be omitted to indicate that the code fix can't be applied in a group. + */ + fixId?: {}; + } + interface CombinedCodeActions { + changes: ReadonlyArray; + commands: ReadonlyArray | undefined; + } type CodeActionCommand = InstallPackageAction; interface InstallPackageAction { } @@ -5027,6 +5043,7 @@ declare namespace ts.server.protocol { DocCommentTemplate = "docCommentTemplate", CompilerOptionsForInferredProjects = "compilerOptionsForInferredProjects", GetCodeFixes = "getCodeFixes", + GetCombinedCodeFix = "getCombinedCodeFix", ApplyCodeActionCommand = "applyCodeActionCommand", GetSupportedCodeFixes = "getSupportedCodeFixes", GetApplicableRefactors = "getApplicableRefactors", @@ -5389,6 +5406,13 @@ declare namespace ts.server.protocol { command: CommandTypes.GetCodeFixes; arguments: CodeFixRequestArgs; } + interface GetCombinedCodeFixRequest extends Request { + command: CommandTypes.GetCombinedCodeFix; + arguments: GetCombinedCodeFixRequestArgs; + } + interface GetCombinedCodeFixResponse extends Response { + body: CombinedCodeActions; + } interface ApplyCodeActionCommandRequest extends Request { command: CommandTypes.ApplyCodeActionCommand; arguments: ApplyCodeActionCommandRequestArgs; @@ -5422,6 +5446,14 @@ declare namespace ts.server.protocol { */ errorCodes?: ReadonlyArray; } + interface GetCombinedCodeFixRequestArgs { + scope: GetCombinedCodeFixScope; + fixId: {}; + } + interface GetCombinedCodeFixScope { + type: "file"; + args: FileRequestArgs; + } interface ApplyCodeActionCommandRequestArgs { /** May also be an array of commands. */ command: {}; @@ -6164,7 +6196,7 @@ declare namespace ts.server.protocol { } interface CodeFixResponse extends Response { /** The code actions that are available */ - body?: CodeAction[]; + body?: CodeFixAction[]; } interface CodeAction { /** Description of the code action to display in the UI of the editor */ @@ -6174,6 +6206,17 @@ declare namespace ts.server.protocol { /** A command is an opaque object that should be passed to `ApplyCodeActionCommandRequestArgs` without modification. */ commands?: {}[]; } + interface CombinedCodeActions { + changes: ReadonlyArray; + commands?: ReadonlyArray<{}>; + } + interface CodeFixAction extends CodeAction { + /** + * If present, one may call 'getCombinedCodeFix' with this fixId. + * This may be omitted to indicate that the code fix can't be applied in a group. + */ + fixId?: {}; + } /** * Format and format on key response message. */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index ce90fef044e..d5259f39f7f 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4091,7 +4091,8 @@ declare namespace ts { getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion; isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: ReadonlyArray, formatOptions: FormatCodeSettings): ReadonlyArray; + getCombinedCodeFix(scope: CombinedCodeFixScope, fixId: {}, formatOptions: FormatCodeSettings): CombinedCodeActions; applyCodeActionCommand(action: CodeActionCommand): Promise; applyCodeActionCommand(action: CodeActionCommand[]): Promise; applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise; @@ -4107,6 +4108,10 @@ declare namespace ts { getProgram(): Program; dispose(): void; } + interface CombinedCodeFixScope { + type: "file"; + fileName: string; + } interface GetCompletionsAtPositionOptions { includeExternalModuleExports: boolean; includeInsertTextCompletions: boolean; @@ -4184,6 +4189,17 @@ declare namespace ts { */ commands?: CodeActionCommand[]; } + interface CodeFixAction extends CodeAction { + /** + * If present, one may call 'getCombinedCodeFix' with this fixId. + * This may be omitted to indicate that the code fix can't be applied in a group. + */ + fixId?: {}; + } + interface CombinedCodeActions { + changes: ReadonlyArray; + commands: ReadonlyArray | undefined; + } type CodeActionCommand = InstallPackageAction; interface InstallPackageAction { } From e248d08e4c7b4e8a87d0844dd6068bfafd1f9d7b Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 17 Jan 2018 12:43:41 -0800 Subject: [PATCH 115/172] Combine `repeatString` helper functions (#21235) --- src/harness/fourslash.ts | 16 ++++------------ src/services/formatting/formatting.ts | 17 ++++------------- src/services/utilities.ts | 8 ++++++++ 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 84bf3bb1752..df870034270 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1535,8 +1535,8 @@ Actual: ${stringify(fullActual)}`); const addSpanInfoString = () => { if (previousSpanInfo) { resultString += currentLine; - let thisLineMarker = repeatString(startColumn, " ") + repeatString(length, "~"); - thisLineMarker += repeatString(this.alignmentForExtraInfo - thisLineMarker.length - prefixString.length + 1, " "); + let thisLineMarker = ts.repeatString(" ", startColumn) + ts.repeatString("~", length); + thisLineMarker += ts.repeatString(" ", this.alignmentForExtraInfo - thisLineMarker.length - prefixString.length + 1); resultString += thisLineMarker; resultString += "=> Pos: (" + (pos - length) + " to " + (pos - 1) + ") "; resultString += " " + previousSpanInfo; @@ -1551,7 +1551,7 @@ Actual: ${stringify(fullActual)}`); if (resultString.length) { resultString += "\n--------------------------------"; } - currentLine = "\n" + nextLine.toString() + repeatString(3 - nextLine.toString().length, " ") + ">" + this.activeFile.content.substring(pos, fileLineMap[nextLine]) + "\n "; + currentLine = "\n" + nextLine.toString() + ts.repeatString(" ", 3 - nextLine.toString().length) + ">" + this.activeFile.content.substring(pos, fileLineMap[nextLine]) + "\n "; startColumn = 0; length = 0; } @@ -2787,7 +2787,7 @@ Actual: ${stringify(fullActual)}`); const items = this.languageService.getNavigationBarItems(this.activeFile.fileName); Harness.IO.log(`Navigation bar (${items.length} items)`); for (const item of items) { - Harness.IO.log(`${repeatString(item.indent, " ")}name: ${item.text}, kind: ${item.kind}, childItems: ${item.childItems.map(child => child.text)}`); + Harness.IO.log(`${ts.repeatString(" ", item.indent)}name: ${item.text}, kind: ${item.kind}, childItems: ${item.childItems.map(child => child.text)}`); } } @@ -3689,14 +3689,6 @@ ${code} }; } - function repeatString(count: number, char: string) { - let result = ""; - for (let i = 0; i < count; i++) { - result += char; - } - return result; - } - function stringify(data: any, replacer?: (key: string, value: any) => any): string { return JSON.stringify(data, replacer, 2); } diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index 49fb1420db8..c020e638692 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -1277,13 +1277,13 @@ namespace ts.formatting { } if (internedTabsIndentation[tabs] === undefined) { - internedTabsIndentation[tabs] = tabString = repeat("\t", tabs); + internedTabsIndentation[tabs] = tabString = repeatString("\t", tabs); } else { tabString = internedTabsIndentation[tabs]; } - return spaces ? tabString + repeat(" ", spaces) : tabString; + return spaces ? tabString + repeatString(" ", spaces) : tabString; } else { let spacesString: string; @@ -1294,23 +1294,14 @@ namespace ts.formatting { } if (internedSpacesIndentation[quotient] === undefined) { - spacesString = repeat(" ", options.indentSize * quotient); + spacesString = repeatString(" ", options.indentSize * quotient); internedSpacesIndentation[quotient] = spacesString; } else { spacesString = internedSpacesIndentation[quotient]; } - return remainder ? spacesString + repeat(" ", remainder) : spacesString; - } - - function repeat(value: string, count: number): string { - let s = ""; - for (let i = 0; i < count; i++) { - s += value; - } - - return s; + return remainder ? spacesString + repeatString(" ", remainder) : spacesString; } } } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index abfe5a7a55d..344a44a9f18 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1110,6 +1110,14 @@ namespace ts { export function getSnapshotText(snap: IScriptSnapshot): string { return snap.getText(0, snap.getLength()); } + + export function repeatString(str: string, count: number): string { + let result = ""; + for (let i = 0; i < count; i++) { + result += str; + } + return result; + } } // Display-part writer helpers From ea6d7e91757dbaad991f9424b321cdd3cada0f37 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 17 Jan 2018 12:44:03 -0800 Subject: [PATCH 116/172] Make issue template more enthusiastic --- issue_template.md | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/issue_template.md b/issue_template.md index 22acf3e9cc5..4fc8cd3ad95 100644 --- a/issue_template.md +++ b/issue_template.md @@ -1,10 +1,33 @@ - - - + + + + + + + + **TypeScript Version:** 2.7.0-dev.201xxxxx + +**Search Terms:** + **Code** ```ts @@ -16,4 +39,6 @@ **Actual behavior:** -**Related:** +**Playground Link:** + +**Related Issues:** From 7e1e038cf30450a554b886b48f730dd20ac0c30b Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 17 Jan 2018 13:17:56 -0800 Subject: [PATCH 117/172] Update issue_template.md --- issue_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/issue_template.md b/issue_template.md index 4fc8cd3ad95..5e3d612c3c3 100644 --- a/issue_template.md +++ b/issue_template.md @@ -14,7 +14,7 @@ Please help us by doing the following steps before logging an issue: --> From 5c889299f4ddb4ba53b704a423da01e2d21275b9 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 17 Jan 2018 13:21:10 -0800 Subject: [PATCH 118/172] Indexed access relation check object+index types Previously, it only check the object types, and only if the index types were identical. Now both checks call `isRelatedTo` recursively. --- src/compiler/checker.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 489fff16e2c..0cfb6d1a381 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9632,7 +9632,7 @@ namespace ts { } else if (target.flags & TypeFlags.IndexedAccess) { // A type S is related to a type T[K] if S is related to A[K], where K is string-like and - // A is the apparent type of T. + // A is the constraint of T. const constraint = getConstraintOfIndexedAccess(target); if (constraint) { if (result = isRelatedTo(source, constraint, reportErrors)) { @@ -9668,7 +9668,7 @@ namespace ts { } else if (source.flags & TypeFlags.IndexedAccess) { // A type S[K] is related to a type T if A[K] is related to T, where K is string-like and - // A is the apparent type of S. + // A is the constraint of S. const constraint = getConstraintOfIndexedAccess(source); if (constraint) { if (result = isRelatedTo(constraint, target, reportErrors)) { @@ -9676,10 +9676,11 @@ namespace ts { return result; } } - else if (target.flags & TypeFlags.IndexedAccess && (source).indexType === (target).indexType) { - // if we have indexed access types with identical index types, see if relationship holds for - // the two object types. + else if (target.flags & TypeFlags.IndexedAccess) { if (result = isRelatedTo((source).objectType, (target).objectType, reportErrors)) { + result &= isRelatedTo((source).indexType, (target).indexType, reportErrors); + } + if (result) { errorInfo = saveErrorInfo; return result; } From 485ec34e8ed7d3240acc567c2287dd822ca2aea8 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 17 Jan 2018 13:22:31 -0800 Subject: [PATCH 119/172] Test assignability of indexed access types --- .../reference/keyofAndIndexedAccess.js | 24 +++++ .../reference/keyofAndIndexedAccess.symbols | 45 +++++++++ .../reference/keyofAndIndexedAccess.types | 48 ++++++++++ .../keyofAndIndexedAccessErrors.errors.txt | 53 +++++++++-- .../reference/keyofAndIndexedAccessErrors.js | 37 ++++++-- .../keyofAndIndexedAccessErrors.symbols | 90 ++++++++++++++---- .../keyofAndIndexedAccessErrors.types | 93 ++++++++++++++++--- .../types/keyof/keyofAndIndexedAccess.ts | 13 +++ .../keyof/keyofAndIndexedAccessErrors.ts | 21 ++++- 9 files changed, 377 insertions(+), 47 deletions(-) diff --git a/tests/baselines/reference/keyofAndIndexedAccess.js b/tests/baselines/reference/keyofAndIndexedAccess.js index 8ea17554b35..2f59ad03510 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.js +++ b/tests/baselines/reference/keyofAndIndexedAccess.js @@ -552,6 +552,19 @@ class AnotherSampleClass extends SampleClass { } } new AnotherSampleClass({}); + +// Positive repro from #17166 +function f3(t: T, k: K, tk: T[K]): void { + for (let key in t) { + key = k // ok, K ==> keyof T + t[key] = tk; // ok, T[K] ==> T[keyof T] + } +} + +// # 21185 +type Predicates = { + [T in keyof TaggedRecord]: (variant: TaggedRecord[keyof TaggedRecord]) => variant is TaggedRecord[T] +} //// [keyofAndIndexedAccess.js] @@ -928,6 +941,13 @@ var AnotherSampleClass = /** @class */ (function (_super) { return AnotherSampleClass; }(SampleClass)); new AnotherSampleClass({}); +// Positive repro from #17166 +function f3(t, k, tk) { + for (var key in t) { + key = k; // ok, K ==> keyof T + t[key] = tk; // ok, T[K] ==> T[keyof T] + } +} //// [keyofAndIndexedAccess.d.ts] @@ -1188,3 +1208,7 @@ declare class AnotherSampleClass extends SampleClass { constructor(props: T); brokenMethod(): void; } +declare function f3(t: T, k: K, tk: T[K]): void; +declare type Predicates = { + [T in keyof TaggedRecord]: (variant: TaggedRecord[keyof TaggedRecord]) => variant is TaggedRecord[T]; +}; diff --git a/tests/baselines/reference/keyofAndIndexedAccess.symbols b/tests/baselines/reference/keyofAndIndexedAccess.symbols index 461652fb725..c1a3449991f 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.symbols +++ b/tests/baselines/reference/keyofAndIndexedAccess.symbols @@ -1962,3 +1962,48 @@ class AnotherSampleClass extends SampleClass { new AnotherSampleClass({}); >AnotherSampleClass : Symbol(AnotherSampleClass, Decl(keyofAndIndexedAccess.ts, 540, 54)) +// Positive repro from #17166 +function f3(t: T, k: K, tk: T[K]): void { +>f3 : Symbol(f3, Decl(keyofAndIndexedAccess.ts, 552, 27)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 555, 12)) +>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 555, 14)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 555, 12)) +>t : Symbol(t, Decl(keyofAndIndexedAccess.ts, 555, 34)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 555, 12)) +>k : Symbol(k, Decl(keyofAndIndexedAccess.ts, 555, 39)) +>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 555, 14)) +>tk : Symbol(tk, Decl(keyofAndIndexedAccess.ts, 555, 45)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 555, 12)) +>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 555, 14)) + + for (let key in t) { +>key : Symbol(key, Decl(keyofAndIndexedAccess.ts, 556, 12)) +>t : Symbol(t, Decl(keyofAndIndexedAccess.ts, 555, 34)) + + key = k // ok, K ==> keyof T +>key : Symbol(key, Decl(keyofAndIndexedAccess.ts, 556, 12)) +>k : Symbol(k, Decl(keyofAndIndexedAccess.ts, 555, 39)) + + t[key] = tk; // ok, T[K] ==> T[keyof T] +>t : Symbol(t, Decl(keyofAndIndexedAccess.ts, 555, 34)) +>key : Symbol(key, Decl(keyofAndIndexedAccess.ts, 556, 12)) +>tk : Symbol(tk, Decl(keyofAndIndexedAccess.ts, 555, 45)) + } +} + +// # 21185 +type Predicates = { +>Predicates : Symbol(Predicates, Decl(keyofAndIndexedAccess.ts, 560, 1)) +>TaggedRecord : Symbol(TaggedRecord, Decl(keyofAndIndexedAccess.ts, 563, 16)) + + [T in keyof TaggedRecord]: (variant: TaggedRecord[keyof TaggedRecord]) => variant is TaggedRecord[T] +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 564, 3)) +>TaggedRecord : Symbol(TaggedRecord, Decl(keyofAndIndexedAccess.ts, 563, 16)) +>variant : Symbol(variant, Decl(keyofAndIndexedAccess.ts, 564, 30)) +>TaggedRecord : Symbol(TaggedRecord, Decl(keyofAndIndexedAccess.ts, 563, 16)) +>TaggedRecord : Symbol(TaggedRecord, Decl(keyofAndIndexedAccess.ts, 563, 16)) +>variant : Symbol(variant, Decl(keyofAndIndexedAccess.ts, 564, 30)) +>TaggedRecord : Symbol(TaggedRecord, Decl(keyofAndIndexedAccess.ts, 563, 16)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 564, 3)) +} + diff --git a/tests/baselines/reference/keyofAndIndexedAccess.types b/tests/baselines/reference/keyofAndIndexedAccess.types index 0e7da19c3e2..d60245e183f 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.types +++ b/tests/baselines/reference/keyofAndIndexedAccess.types @@ -2294,3 +2294,51 @@ new AnotherSampleClass({}); >AnotherSampleClass : typeof AnotherSampleClass >{} : {} +// Positive repro from #17166 +function f3(t: T, k: K, tk: T[K]): void { +>f3 : (t: T, k: K, tk: T[K]) => void +>T : T +>K : K +>T : T +>t : T +>T : T +>k : K +>K : K +>tk : T[K] +>T : T +>K : K + + for (let key in t) { +>key : keyof T +>t : T + + key = k // ok, K ==> keyof T +>key = k : K +>key : keyof T +>k : K + + t[key] = tk; // ok, T[K] ==> T[keyof T] +>t[key] = tk : T[K] +>t[key] : T[keyof T] +>t : T +>key : keyof T +>tk : T[K] + } +} + +// # 21185 +type Predicates = { +>Predicates : Predicates +>TaggedRecord : TaggedRecord + + [T in keyof TaggedRecord]: (variant: TaggedRecord[keyof TaggedRecord]) => variant is TaggedRecord[T] +>T : T +>TaggedRecord : TaggedRecord +>variant : TaggedRecord[keyof TaggedRecord] +>TaggedRecord : TaggedRecord +>TaggedRecord : TaggedRecord +>variant : any +>TaggedRecord : TaggedRecord +>T : T +} + diff --git a/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt b/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt index 2981cb0366d..0b2da3f8e61 100644 --- a/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt +++ b/tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt @@ -27,11 +27,21 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(76,5): error Type 'T' is not assignable to type 'T & U'. Type 'T' is not assignable to type 'U'. tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(77,5): error TS2322: Type 'keyof (T & U)' is not assignable to type 'keyof (T | U)'. -tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(84,9): error TS2322: Type 'keyof T' is not assignable to type 'K'. -tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(85,9): error TS2322: Type 'T[keyof T]' is not assignable to type 'T[K]'. +tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(86,9): error TS2322: Type 'keyof T' is not assignable to type 'K'. +tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(88,9): error TS2322: Type 'T[keyof T]' is not assignable to type 'T[K]'. + Type 'keyof T' is not assignable to type 'K'. +tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(91,5): error TS2322: Type 'T[K]' is not assignable to type 'U[K]'. + Type 'T' is not assignable to type 'U'. +tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(94,5): error TS2322: Type 'T[J]' is not assignable to type 'U[J]'. + Type 'T' is not assignable to type 'U'. +tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(97,5): error TS2322: Type 'T[K]' is not assignable to type 'T[J]'. + Type 'K' is not assignable to type 'J'. + Type 'keyof T' is not assignable to type 'J'. +tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(100,5): error TS2322: Type 'T[K]' is not assignable to type 'U[J]'. + Type 'T' is not assignable to type 'U'. -==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (27 errors) ==== +==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (31 errors) ==== class Shape { name: string; width: number; @@ -167,15 +177,42 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(85,9): error } // Repro from #17166 - function f3(obj: T, k: K, value: T[K]): void { - for (let key in obj) { + function f3( + t: T, k: K, tk: T[K], u: U, j: J, uk: U[K], tj: T[J], uj: U[J]): void { + for (let key in t) { + key = k // ok, K ==> keyof T k = key // error, keyof T =/=> K ~ !!! error TS2322: Type 'keyof T' is not assignable to type 'K'. - value = obj[key]; // error, T[keyof T] =/=> T[K] - ~~~~~ + t[key] = tk; // ok, T[K] ==> T[keyof T] + tk = t[key]; // error, T[keyof T] =/=> T[K] + ~~ !!! error TS2322: Type 'T[keyof T]' is not assignable to type 'T[K]'. +!!! error TS2322: Type 'keyof T' is not assignable to type 'K'. } - } + tk = uk; + uk = tk; // error + ~~ +!!! error TS2322: Type 'T[K]' is not assignable to type 'U[K]'. +!!! error TS2322: Type 'T' is not assignable to type 'U'. + tj = uj; + uj = tj; // error + ~~ +!!! error TS2322: Type 'T[J]' is not assignable to type 'U[J]'. +!!! error TS2322: Type 'T' is not assignable to type 'U'. + + tk = tj; + tj = tk; // error + ~~ +!!! error TS2322: Type 'T[K]' is not assignable to type 'T[J]'. +!!! error TS2322: Type 'K' is not assignable to type 'J'. +!!! error TS2322: Type 'keyof T' is not assignable to type 'J'. + + tk = uj; + uj = tk; // error + ~~ +!!! error TS2322: Type 'T[K]' is not assignable to type 'U[J]'. +!!! error TS2322: Type 'T' is not assignable to type 'U'. + } \ No newline at end of file diff --git a/tests/baselines/reference/keyofAndIndexedAccessErrors.js b/tests/baselines/reference/keyofAndIndexedAccessErrors.js index 2a1042cf4ee..d267699ffd6 100644 --- a/tests/baselines/reference/keyofAndIndexedAccessErrors.js +++ b/tests/baselines/reference/keyofAndIndexedAccessErrors.js @@ -80,13 +80,26 @@ function f20(k1: keyof (T | U), k2: keyof (T & U), o1: T | U, o2: T & U) { } // Repro from #17166 -function f3(obj: T, k: K, value: T[K]): void { - for (let key in obj) { +function f3( + t: T, k: K, tk: T[K], u: U, j: J, uk: U[K], tj: T[J], uj: U[J]): void { + for (let key in t) { + key = k // ok, K ==> keyof T k = key // error, keyof T =/=> K - value = obj[key]; // error, T[keyof T] =/=> T[K] + t[key] = tk; // ok, T[K] ==> T[keyof T] + tk = t[key]; // error, T[keyof T] =/=> T[K] } -} + tk = uk; + uk = tk; // error + tj = uj; + uj = tj; // error + + tk = tj; + tj = tk; // error + + tk = uj; + uj = tk; // error +} //// [keyofAndIndexedAccessErrors.js] @@ -120,9 +133,19 @@ function f20(k1, k2, o1, o2) { k2 = k1; } // Repro from #17166 -function f3(obj, k, value) { - for (var key in obj) { +function f3(t, k, tk, u, j, uk, tj, uj) { + for (var key in t) { + key = k; // ok, K ==> keyof T k = key; // error, keyof T =/=> K - value = obj[key]; // error, T[keyof T] =/=> T[K] + t[key] = tk; // ok, T[K] ==> T[keyof T] + tk = t[key]; // error, T[keyof T] =/=> T[K] } + tk = uk; + uk = tk; // error + tj = uj; + uj = tj; // error + tk = tj; + tj = tk; // error + tk = uj; + uj = tk; // error } diff --git a/tests/baselines/reference/keyofAndIndexedAccessErrors.symbols b/tests/baselines/reference/keyofAndIndexedAccessErrors.symbols index 0e97ef793fb..47b22af2541 100644 --- a/tests/baselines/reference/keyofAndIndexedAccessErrors.symbols +++ b/tests/baselines/reference/keyofAndIndexedAccessErrors.symbols @@ -270,32 +270,90 @@ function f20(k1: keyof (T | U), k2: keyof (T & U), o1: T | U, o2: T & U) { } // Repro from #17166 -function f3(obj: T, k: K, value: T[K]): void { +function f3( >f3 : Symbol(f3, Decl(keyofAndIndexedAccessErrors.ts, 78, 1)) >T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12)) >K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14)) >T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12)) ->obj : Symbol(obj, Decl(keyofAndIndexedAccessErrors.ts, 81, 34)) ->T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12)) ->k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 81, 41)) ->K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14)) ->value : Symbol(value, Decl(keyofAndIndexedAccessErrors.ts, 81, 47)) +>U : Symbol(U, Decl(keyofAndIndexedAccessErrors.ts, 81, 33)) >T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12)) +>J : Symbol(J, Decl(keyofAndIndexedAccessErrors.ts, 81, 46)) >K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14)) - for (let key in obj) { ->key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 82, 12)) ->obj : Symbol(obj, Decl(keyofAndIndexedAccessErrors.ts, 81, 34)) + t: T, k: K, tk: T[K], u: U, j: J, uk: U[K], tj: T[J], uj: U[J]): void { +>t : Symbol(t, Decl(keyofAndIndexedAccessErrors.ts, 81, 60)) +>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12)) +>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 82, 9)) +>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14)) +>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 82, 15)) +>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12)) +>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14)) +>u : Symbol(u, Decl(keyofAndIndexedAccessErrors.ts, 82, 25)) +>U : Symbol(U, Decl(keyofAndIndexedAccessErrors.ts, 81, 33)) +>j : Symbol(j, Decl(keyofAndIndexedAccessErrors.ts, 82, 31)) +>J : Symbol(J, Decl(keyofAndIndexedAccessErrors.ts, 81, 46)) +>uk : Symbol(uk, Decl(keyofAndIndexedAccessErrors.ts, 82, 37)) +>U : Symbol(U, Decl(keyofAndIndexedAccessErrors.ts, 81, 33)) +>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 81, 14)) +>tj : Symbol(tj, Decl(keyofAndIndexedAccessErrors.ts, 82, 47)) +>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 81, 12)) +>J : Symbol(J, Decl(keyofAndIndexedAccessErrors.ts, 81, 46)) +>uj : Symbol(uj, Decl(keyofAndIndexedAccessErrors.ts, 82, 57)) +>U : Symbol(U, Decl(keyofAndIndexedAccessErrors.ts, 81, 33)) +>J : Symbol(J, Decl(keyofAndIndexedAccessErrors.ts, 81, 46)) + + for (let key in t) { +>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 83, 12)) +>t : Symbol(t, Decl(keyofAndIndexedAccessErrors.ts, 81, 60)) + + key = k // ok, K ==> keyof T +>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 83, 12)) +>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 82, 9)) k = key // error, keyof T =/=> K ->k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 81, 41)) ->key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 82, 12)) +>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 82, 9)) +>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 83, 12)) - value = obj[key]; // error, T[keyof T] =/=> T[K] ->value : Symbol(value, Decl(keyofAndIndexedAccessErrors.ts, 81, 47)) ->obj : Symbol(obj, Decl(keyofAndIndexedAccessErrors.ts, 81, 34)) ->key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 82, 12)) + t[key] = tk; // ok, T[K] ==> T[keyof T] +>t : Symbol(t, Decl(keyofAndIndexedAccessErrors.ts, 81, 60)) +>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 83, 12)) +>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 82, 15)) + + tk = t[key]; // error, T[keyof T] =/=> T[K] +>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 82, 15)) +>t : Symbol(t, Decl(keyofAndIndexedAccessErrors.ts, 81, 60)) +>key : Symbol(key, Decl(keyofAndIndexedAccessErrors.ts, 83, 12)) } + tk = uk; +>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 82, 15)) +>uk : Symbol(uk, Decl(keyofAndIndexedAccessErrors.ts, 82, 37)) + + uk = tk; // error +>uk : Symbol(uk, Decl(keyofAndIndexedAccessErrors.ts, 82, 37)) +>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 82, 15)) + + tj = uj; +>tj : Symbol(tj, Decl(keyofAndIndexedAccessErrors.ts, 82, 47)) +>uj : Symbol(uj, Decl(keyofAndIndexedAccessErrors.ts, 82, 57)) + + uj = tj; // error +>uj : Symbol(uj, Decl(keyofAndIndexedAccessErrors.ts, 82, 57)) +>tj : Symbol(tj, Decl(keyofAndIndexedAccessErrors.ts, 82, 47)) + + tk = tj; +>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 82, 15)) +>tj : Symbol(tj, Decl(keyofAndIndexedAccessErrors.ts, 82, 47)) + + tj = tk; // error +>tj : Symbol(tj, Decl(keyofAndIndexedAccessErrors.ts, 82, 47)) +>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 82, 15)) + + tk = uj; +>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 82, 15)) +>uj : Symbol(uj, Decl(keyofAndIndexedAccessErrors.ts, 82, 57)) + + uj = tk; // error +>uj : Symbol(uj, Decl(keyofAndIndexedAccessErrors.ts, 82, 57)) +>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 82, 15)) } - diff --git a/tests/baselines/reference/keyofAndIndexedAccessErrors.types b/tests/baselines/reference/keyofAndIndexedAccessErrors.types index f5cc7c093d9..0c51d3a575c 100644 --- a/tests/baselines/reference/keyofAndIndexedAccessErrors.types +++ b/tests/baselines/reference/keyofAndIndexedAccessErrors.types @@ -301,35 +301,104 @@ function f20(k1: keyof (T | U), k2: keyof (T & U), o1: T | U, o2: T & U) { } // Repro from #17166 -function f3(obj: T, k: K, value: T[K]): void { ->f3 : (obj: T, k: K, value: T[K]) => void +function f3( +>f3 : (t: T, k: K, tk: T[K], u: U, j: J, uk: U[K], tj: T[J], uj: U[J]) => void >T : T >K : K >T : T ->obj : T +>U : U +>T : T +>J : J +>K : K + + t: T, k: K, tk: T[K], u: U, j: J, uk: U[K], tj: T[J], uj: U[J]): void { +>t : T >T : T >k : K >K : K ->value : T[K] +>tk : T[K] >T : T >K : K +>u : U +>U : U +>j : J +>J : J +>uk : U[K] +>U : U +>K : K +>tj : T[J] +>T : T +>J : J +>uj : U[J] +>U : U +>J : J - for (let key in obj) { + for (let key in t) { >key : keyof T ->obj : T +>t : T + + key = k // ok, K ==> keyof T +>key = k : K +>key : keyof T +>k : K k = key // error, keyof T =/=> K >k = key : keyof T >k : K >key : keyof T - value = obj[key]; // error, T[keyof T] =/=> T[K] ->value = obj[key] : T[keyof T] ->value : T[K] ->obj[key] : T[keyof T] ->obj : T + t[key] = tk; // ok, T[K] ==> T[keyof T] +>t[key] = tk : T[K] +>t[key] : T[keyof T] +>t : T +>key : keyof T +>tk : T[K] + + tk = t[key]; // error, T[keyof T] =/=> T[K] +>tk = t[key] : T[keyof T] +>tk : T[K] +>t[key] : T[keyof T] +>t : T >key : keyof T } + tk = uk; +>tk = uk : U[K] +>tk : T[K] +>uk : U[K] + + uk = tk; // error +>uk = tk : T[K] +>uk : U[K] +>tk : T[K] + + tj = uj; +>tj = uj : U[J] +>tj : T[J] +>uj : U[J] + + uj = tj; // error +>uj = tj : T[J] +>uj : U[J] +>tj : T[J] + + tk = tj; +>tk = tj : T[J] +>tk : T[K] +>tj : T[J] + + tj = tk; // error +>tj = tk : T[K] +>tj : T[J] +>tk : T[K] + + tk = uj; +>tk = uj : U[J] +>tk : T[K] +>uj : U[J] + + uj = tk; // error +>uj = tk : T[K] +>uj : U[J] +>tk : T[K] } - diff --git a/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts b/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts index 70b70dff6ea..9ec4e820f73 100644 --- a/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts +++ b/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts @@ -554,3 +554,16 @@ class AnotherSampleClass extends SampleClass { } } new AnotherSampleClass({}); + +// Positive repro from #17166 +function f3(t: T, k: K, tk: T[K]): void { + for (let key in t) { + key = k // ok, K ==> keyof T + t[key] = tk; // ok, T[K] ==> T[keyof T] + } +} + +// # 21185 +type Predicates = { + [T in keyof TaggedRecord]: (variant: TaggedRecord[keyof TaggedRecord]) => variant is TaggedRecord[T] +} diff --git a/tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts b/tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts index ffc5076831a..f96bcdb6234 100644 --- a/tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts +++ b/tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts @@ -79,10 +79,23 @@ function f20(k1: keyof (T | U), k2: keyof (T & U), o1: T | U, o2: T & U) { } // Repro from #17166 -function f3(obj: T, k: K, value: T[K]): void { - for (let key in obj) { +function f3( + t: T, k: K, tk: T[K], u: U, j: J, uk: U[K], tj: T[J], uj: U[J]): void { + for (let key in t) { + key = k // ok, K ==> keyof T k = key // error, keyof T =/=> K - value = obj[key]; // error, T[keyof T] =/=> T[K] + t[key] = tk; // ok, T[K] ==> T[keyof T] + tk = t[key]; // error, T[keyof T] =/=> T[K] } -} + tk = uk; + uk = tk; // error + tj = uj; + uj = tj; // error + + tk = tj; + tj = tk; // error + + tk = uj; + uj = tk; // error +} From b0d7d5a7ef15c22c07a517213bf0b8aa2265e90c Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 17 Jan 2018 14:16:11 -0800 Subject: [PATCH 120/172] Fix #21089: Do not infer from numeric index signature in Object.values and Object.entries (#21129) * Fix https://github.com/Microsoft/TypeScript/issues/21089: Do not infer from numeric index signature in Object.values and Object.entries * Update test --- src/lib/es2017.object.d.ts | 4 +- .../reference/useObjectValuesAndEntries1.js | 54 +++++-- .../useObjectValuesAndEntries1.symbols | 102 +++++++++++-- .../useObjectValuesAndEntries1.types | 138 +++++++++++++++--- .../useObjectValuesAndEntries4.types | 8 +- .../es2017/useObjectValuesAndEntries1.ts | 28 +++- 6 files changed, 289 insertions(+), 45 deletions(-) diff --git a/src/lib/es2017.object.d.ts b/src/lib/es2017.object.d.ts index 775aaece7f6..1790ce76894 100644 --- a/src/lib/es2017.object.d.ts +++ b/src/lib/es2017.object.d.ts @@ -3,7 +3,7 @@ interface ObjectConstructor { * Returns an array of values of the enumerable properties of an object * @param o Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object. */ - values(o: { [s: string]: T } | { [n: number]: T }): T[]; + values(o: { [s: string]: T } | ArrayLike): T[]; /** * Returns an array of values of the enumerable properties of an object @@ -15,7 +15,7 @@ interface ObjectConstructor { * Returns an array of key/values of the enumerable properties of an object * @param o Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object. */ - entries(o: { [s: string]: T } | { [n: number]: T }): [string, T][]; + entries(o: { [s: string]: T } | ArrayLike): [string, T][]; /** * Returns an array of key/values of the enumerable properties of an object diff --git a/tests/baselines/reference/useObjectValuesAndEntries1.js b/tests/baselines/reference/useObjectValuesAndEntries1.js index a10fb9a2d32..7b18fb1fcd9 100644 --- a/tests/baselines/reference/useObjectValuesAndEntries1.js +++ b/tests/baselines/reference/useObjectValuesAndEntries1.js @@ -5,11 +5,30 @@ for (var x of Object.values(o)) { let y = x; } -var entries = Object.entries(o); -var entries1 = Object.entries(1); -var entries2 = Object.entries({a: true, b: 2}) -var entries3 = Object.entries({}) - +var entries = Object.entries(o); // [string, number][] +var values = Object.values(o); // number[] + +var entries1 = Object.entries(1); // [string, any][] +var values1 = Object.values(1); // any[] + +var entries2 = Object.entries({ a: true, b: 2 }); // [string, number|boolean][] +var values2 = Object.values({ a: true, b: 2 }); // (number|boolean)[] + +var entries3 = Object.entries({}); // [string, {}][] +var values3 = Object.values({}); // {}[] + +var a = ["a", "b", "c"]; +var entries4 = Object.entries(a); // [string, string][] +var values4 = Object.values(a); // string[] + +enum E { A, B } +var entries5 = Object.entries(E); // [string, any][] +var values5 = Object.values(E); // any[] + +interface I { } +var i: I = {}; +var entries6 = Object.entries(i); // [string, any][] +var values6 = Object.values(i); // any[] //// [useObjectValuesAndEntries1.js] var o = { a: 1, b: 2 }; @@ -17,7 +36,24 @@ for (var _i = 0, _a = Object.values(o); _i < _a.length; _i++) { var x = _a[_i]; var y = x; } -var entries = Object.entries(o); -var entries1 = Object.entries(1); -var entries2 = Object.entries({ a: true, b: 2 }); -var entries3 = Object.entries({}); +var entries = Object.entries(o); // [string, number][] +var values = Object.values(o); // number[] +var entries1 = Object.entries(1); // [string, any][] +var values1 = Object.values(1); // any[] +var entries2 = Object.entries({ a: true, b: 2 }); // [string, number|boolean][] +var values2 = Object.values({ a: true, b: 2 }); // (number|boolean)[] +var entries3 = Object.entries({}); // [string, {}][] +var values3 = Object.values({}); // {}[] +var a = ["a", "b", "c"]; +var entries4 = Object.entries(a); // [string, string][] +var values4 = Object.values(a); // string[] +var E; +(function (E) { + E[E["A"] = 0] = "A"; + E[E["B"] = 1] = "B"; +})(E || (E = {})); +var entries5 = Object.entries(E); // [string, any][] +var values5 = Object.values(E); // any[] +var i = {}; +var entries6 = Object.entries(i); // [string, any][] +var values6 = Object.values(i); // any[] diff --git a/tests/baselines/reference/useObjectValuesAndEntries1.symbols b/tests/baselines/reference/useObjectValuesAndEntries1.symbols index 538e3f69d78..ee9adbdac8f 100644 --- a/tests/baselines/reference/useObjectValuesAndEntries1.symbols +++ b/tests/baselines/reference/useObjectValuesAndEntries1.symbols @@ -16,30 +16,114 @@ for (var x of Object.values(o)) { >x : Symbol(x, Decl(useObjectValuesAndEntries1.ts, 2, 8)) } -var entries = Object.entries(o); +var entries = Object.entries(o); // [string, number][] >entries : Symbol(entries, Decl(useObjectValuesAndEntries1.ts, 6, 3)) >Object.entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) >Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) >entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) >o : Symbol(o, Decl(useObjectValuesAndEntries1.ts, 0, 3)) -var entries1 = Object.entries(1); ->entries1 : Symbol(entries1, Decl(useObjectValuesAndEntries1.ts, 7, 3)) +var values = Object.values(o); // number[] +>values : Symbol(values, Decl(useObjectValuesAndEntries1.ts, 7, 3)) +>Object.values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>o : Symbol(o, Decl(useObjectValuesAndEntries1.ts, 0, 3)) + +var entries1 = Object.entries(1); // [string, any][] +>entries1 : Symbol(entries1, Decl(useObjectValuesAndEntries1.ts, 9, 3)) >Object.entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) >Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) >entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) -var entries2 = Object.entries({a: true, b: 2}) ->entries2 : Symbol(entries2, Decl(useObjectValuesAndEntries1.ts, 8, 3)) +var values1 = Object.values(1); // any[] +>values1 : Symbol(values1, Decl(useObjectValuesAndEntries1.ts, 10, 3)) +>Object.values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) + +var entries2 = Object.entries({ a: true, b: 2 }); // [string, number|boolean][] +>entries2 : Symbol(entries2, Decl(useObjectValuesAndEntries1.ts, 12, 3)) >Object.entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) >Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) >entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) ->a : Symbol(a, Decl(useObjectValuesAndEntries1.ts, 8, 31)) ->b : Symbol(b, Decl(useObjectValuesAndEntries1.ts, 8, 39)) +>a : Symbol(a, Decl(useObjectValuesAndEntries1.ts, 12, 31)) +>b : Symbol(b, Decl(useObjectValuesAndEntries1.ts, 12, 40)) -var entries3 = Object.entries({}) ->entries3 : Symbol(entries3, Decl(useObjectValuesAndEntries1.ts, 9, 3)) +var values2 = Object.values({ a: true, b: 2 }); // (number|boolean)[] +>values2 : Symbol(values2, Decl(useObjectValuesAndEntries1.ts, 13, 3)) +>Object.values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>a : Symbol(a, Decl(useObjectValuesAndEntries1.ts, 13, 29)) +>b : Symbol(b, Decl(useObjectValuesAndEntries1.ts, 13, 38)) + +var entries3 = Object.entries({}); // [string, {}][] +>entries3 : Symbol(entries3, Decl(useObjectValuesAndEntries1.ts, 15, 3)) >Object.entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) >Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) >entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +var values3 = Object.values({}); // {}[] +>values3 : Symbol(values3, Decl(useObjectValuesAndEntries1.ts, 16, 3)) +>Object.values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) + +var a = ["a", "b", "c"]; +>a : Symbol(a, Decl(useObjectValuesAndEntries1.ts, 18, 3)) + +var entries4 = Object.entries(a); // [string, string][] +>entries4 : Symbol(entries4, Decl(useObjectValuesAndEntries1.ts, 19, 3)) +>Object.entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>a : Symbol(a, Decl(useObjectValuesAndEntries1.ts, 18, 3)) + +var values4 = Object.values(a); // string[] +>values4 : Symbol(values4, Decl(useObjectValuesAndEntries1.ts, 20, 3)) +>Object.values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>a : Symbol(a, Decl(useObjectValuesAndEntries1.ts, 18, 3)) + +enum E { A, B } +>E : Symbol(E, Decl(useObjectValuesAndEntries1.ts, 20, 31)) +>A : Symbol(E.A, Decl(useObjectValuesAndEntries1.ts, 22, 8)) +>B : Symbol(E.B, Decl(useObjectValuesAndEntries1.ts, 22, 11)) + +var entries5 = Object.entries(E); // [string, any][] +>entries5 : Symbol(entries5, Decl(useObjectValuesAndEntries1.ts, 23, 3)) +>Object.entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>E : Symbol(E, Decl(useObjectValuesAndEntries1.ts, 20, 31)) + +var values5 = Object.values(E); // any[] +>values5 : Symbol(values5, Decl(useObjectValuesAndEntries1.ts, 24, 3)) +>Object.values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>E : Symbol(E, Decl(useObjectValuesAndEntries1.ts, 20, 31)) + +interface I { } +>I : Symbol(I, Decl(useObjectValuesAndEntries1.ts, 24, 31)) + +var i: I = {}; +>i : Symbol(i, Decl(useObjectValuesAndEntries1.ts, 27, 3)) +>I : Symbol(I, Decl(useObjectValuesAndEntries1.ts, 24, 31)) + +var entries6 = Object.entries(i); // [string, any][] +>entries6 : Symbol(entries6, Decl(useObjectValuesAndEntries1.ts, 28, 3)) +>Object.entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>entries : Symbol(ObjectConstructor.entries, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>i : Symbol(i, Decl(useObjectValuesAndEntries1.ts, 27, 3)) + +var values6 = Object.values(i); // any[] +>values6 : Symbol(values6, Decl(useObjectValuesAndEntries1.ts, 29, 3)) +>Object.values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --)) +>i : Symbol(i, Decl(useObjectValuesAndEntries1.ts, 27, 3)) + diff --git a/tests/baselines/reference/useObjectValuesAndEntries1.types b/tests/baselines/reference/useObjectValuesAndEntries1.types index c52d0ce872e..60ad86bcb43 100644 --- a/tests/baselines/reference/useObjectValuesAndEntries1.types +++ b/tests/baselines/reference/useObjectValuesAndEntries1.types @@ -10,9 +10,9 @@ var o = { a: 1, b: 2 }; for (var x of Object.values(o)) { >x : number >Object.values(o) : number[] ->Object.values : { (o: { [s: string]: T; } | { [n: number]: T; }): T[]; (o: {}): any[]; } +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } >Object : ObjectConstructor ->values : { (o: { [s: string]: T; } | { [n: number]: T; }): T[]; (o: {}): any[]; } +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } >o : { a: number; b: number; } let y = x; @@ -20,39 +20,143 @@ for (var x of Object.values(o)) { >x : number } -var entries = Object.entries(o); +var entries = Object.entries(o); // [string, number][] >entries : [string, number][] >Object.entries(o) : [string, number][] ->Object.entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } +>Object.entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } >Object : ObjectConstructor ->entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } +>entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } >o : { a: number; b: number; } -var entries1 = Object.entries(1); +var values = Object.values(o); // number[] +>values : number[] +>Object.values(o) : number[] +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>Object : ObjectConstructor +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>o : { a: number; b: number; } + +var entries1 = Object.entries(1); // [string, any][] >entries1 : [string, any][] >Object.entries(1) : [string, any][] ->Object.entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } +>Object.entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } >Object : ObjectConstructor ->entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } +>entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } >1 : 1 -var entries2 = Object.entries({a: true, b: 2}) ->entries2 : [string, number | boolean][] ->Object.entries({a: true, b: 2}) : [string, number | boolean][] ->Object.entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } +var values1 = Object.values(1); // any[] +>values1 : any[] +>Object.values(1) : any[] +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } >Object : ObjectConstructor ->entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } ->{a: true, b: 2} : { a: boolean; b: number; } +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>1 : 1 + +var entries2 = Object.entries({ a: true, b: 2 }); // [string, number|boolean][] +>entries2 : [string, number | boolean][] +>Object.entries({ a: true, b: 2 }) : [string, number | boolean][] +>Object.entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } +>Object : ObjectConstructor +>entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } +>{ a: true, b: 2 } : { a: boolean; b: number; } >a : boolean >true : true >b : number >2 : 2 -var entries3 = Object.entries({}) +var values2 = Object.values({ a: true, b: 2 }); // (number|boolean)[] +>values2 : (number | boolean)[] +>Object.values({ a: true, b: 2 }) : (number | boolean)[] +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>Object : ObjectConstructor +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>{ a: true, b: 2 } : { a: boolean; b: number; } +>a : boolean +>true : true +>b : number +>2 : 2 + +var entries3 = Object.entries({}); // [string, {}][] >entries3 : [string, {}][] >Object.entries({}) : [string, {}][] ->Object.entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } +>Object.entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } >Object : ObjectConstructor ->entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } +>entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } >{} : {} +var values3 = Object.values({}); // {}[] +>values3 : {}[] +>Object.values({}) : {}[] +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>Object : ObjectConstructor +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>{} : {} + +var a = ["a", "b", "c"]; +>a : string[] +>["a", "b", "c"] : string[] +>"a" : "a" +>"b" : "b" +>"c" : "c" + +var entries4 = Object.entries(a); // [string, string][] +>entries4 : [string, string][] +>Object.entries(a) : [string, string][] +>Object.entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } +>Object : ObjectConstructor +>entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } +>a : string[] + +var values4 = Object.values(a); // string[] +>values4 : string[] +>Object.values(a) : string[] +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>Object : ObjectConstructor +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>a : string[] + +enum E { A, B } +>E : E +>A : E.A +>B : E.B + +var entries5 = Object.entries(E); // [string, any][] +>entries5 : [string, any][] +>Object.entries(E) : [string, any][] +>Object.entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } +>Object : ObjectConstructor +>entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } +>E : typeof E + +var values5 = Object.values(E); // any[] +>values5 : any[] +>Object.values(E) : any[] +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>Object : ObjectConstructor +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>E : typeof E + +interface I { } +>I : I + +var i: I = {}; +>i : I +>I : I +>{} : {} + +var entries6 = Object.entries(i); // [string, any][] +>entries6 : [string, any][] +>Object.entries(i) : [string, any][] +>Object.entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } +>Object : ObjectConstructor +>entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } +>i : I + +var values6 = Object.values(i); // any[] +>values6 : any[] +>Object.values(i) : any[] +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>Object : ObjectConstructor +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } +>i : I + diff --git a/tests/baselines/reference/useObjectValuesAndEntries4.types b/tests/baselines/reference/useObjectValuesAndEntries4.types index 33a427760a8..58aac10e52b 100644 --- a/tests/baselines/reference/useObjectValuesAndEntries4.types +++ b/tests/baselines/reference/useObjectValuesAndEntries4.types @@ -10,9 +10,9 @@ var o = { a: 1, b: 2 }; for (var x of Object.values(o)) { >x : number >Object.values(o) : number[] ->Object.values : { (o: { [s: string]: T; } | { [n: number]: T; }): T[]; (o: {}): any[]; } +>Object.values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } >Object : ObjectConstructor ->values : { (o: { [s: string]: T; } | { [n: number]: T; }): T[]; (o: {}): any[]; } +>values : { (o: { [s: string]: T; } | ArrayLike): T[]; (o: {}): any[]; } >o : { a: number; b: number; } let y = x; @@ -23,8 +23,8 @@ for (var x of Object.values(o)) { var entries = Object.entries(o); >entries : [string, number][] >Object.entries(o) : [string, number][] ->Object.entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } +>Object.entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } >Object : ObjectConstructor ->entries : { (o: { [s: string]: T; } | { [n: number]: T; }): [string, T][]; (o: {}): [string, any][]; } +>entries : { (o: { [s: string]: T; } | ArrayLike): [string, T][]; (o: {}): [string, any][]; } >o : { a: number; b: number; } diff --git a/tests/cases/conformance/es2017/useObjectValuesAndEntries1.ts b/tests/cases/conformance/es2017/useObjectValuesAndEntries1.ts index 719f10bbbcc..7422826be16 100644 --- a/tests/cases/conformance/es2017/useObjectValuesAndEntries1.ts +++ b/tests/cases/conformance/es2017/useObjectValuesAndEntries1.ts @@ -7,7 +7,27 @@ for (var x of Object.values(o)) { let y = x; } -var entries = Object.entries(o); -var entries1 = Object.entries(1); -var entries2 = Object.entries({a: true, b: 2}) -var entries3 = Object.entries({}) +var entries = Object.entries(o); // [string, number][] +var values = Object.values(o); // number[] + +var entries1 = Object.entries(1); // [string, any][] +var values1 = Object.values(1); // any[] + +var entries2 = Object.entries({ a: true, b: 2 }); // [string, number|boolean][] +var values2 = Object.values({ a: true, b: 2 }); // (number|boolean)[] + +var entries3 = Object.entries({}); // [string, {}][] +var values3 = Object.values({}); // {}[] + +var a = ["a", "b", "c"]; +var entries4 = Object.entries(a); // [string, string][] +var values4 = Object.values(a); // string[] + +enum E { A, B } +var entries5 = Object.entries(E); // [string, any][] +var values5 = Object.values(E); // any[] + +interface I { } +var i: I = {}; +var entries6 = Object.entries(i); // [string, any][] +var values6 = Object.values(i); // any[] \ No newline at end of file From ec3765130812677e1be7b3d9909ee312e82ac663 Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 17 Jan 2018 14:22:58 -0800 Subject: [PATCH 121/172] Use packageId for suggestion to install `@types/packageName` (#21241) --- src/compiler/checker.ts | 4 ++-- .../untypedModuleImport_noImplicitAny.errors.txt | 5 ++++- .../untypedModuleImport_noImplicitAny.js | 5 ++++- ...eImport_noImplicitAny_relativePath.errors.txt | 16 ++++++++++++++++ ...pedModuleImport_noImplicitAny_relativePath.js | 15 +++++++++++++++ ...duleImport_noImplicitAny_relativePath.symbols | 4 ++++ ...ModuleImport_noImplicitAny_relativePath.types | 4 ++++ .../untypedModuleImport_noImplicitAny.ts | 4 +++- ...pedModuleImport_noImplicitAny_relativePath.ts | 11 +++++++++++ 9 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.errors.txt create mode 100644 tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.js create mode 100644 tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.symbols create mode 100644 tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.types create mode 100644 tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny_relativePath.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 39679675a31..0e4525c9f2d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2063,9 +2063,9 @@ namespace ts { error(errorNode, diag, moduleReference, resolvedModule.resolvedFileName); } else if (noImplicitAny && moduleNotFoundError) { - let errorInfo = !resolvedModule.isExternalLibraryImport ? undefined : chainDiagnosticMessages(/*details*/ undefined, + let errorInfo = resolvedModule.packageId && chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Try_npm_install_types_Slash_0_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0, - moduleReference); + resolvedModule.packageId.name); errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type, moduleReference, diff --git a/tests/baselines/reference/untypedModuleImport_noImplicitAny.errors.txt b/tests/baselines/reference/untypedModuleImport_noImplicitAny.errors.txt index 0f451941bd7..5137ea4ae5f 100644 --- a/tests/baselines/reference/untypedModuleImport_noImplicitAny.errors.txt +++ b/tests/baselines/reference/untypedModuleImport_noImplicitAny.errors.txt @@ -8,8 +8,11 @@ !!! error TS7016: Could not find a declaration file for module 'foo'. '/node_modules/foo/index.js' implicitly has an 'any' type. !!! error TS7016: Try `npm install @types/foo` if it exists or add a new declaration (.d.ts) file containing `declare module 'foo';` -==== /node_modules/foo/index.js (0 errors) ==== +==== /node_modules/foo/package.json (0 errors) ==== // This tests that `--noImplicitAny` disables untyped modules. + { "name": "foo", "version": "1.2.3" } + +==== /node_modules/foo/index.js (0 errors) ==== This file is not processed. \ No newline at end of file diff --git a/tests/baselines/reference/untypedModuleImport_noImplicitAny.js b/tests/baselines/reference/untypedModuleImport_noImplicitAny.js index 86e2ae3c175..378ebee6d7a 100644 --- a/tests/baselines/reference/untypedModuleImport_noImplicitAny.js +++ b/tests/baselines/reference/untypedModuleImport_noImplicitAny.js @@ -1,8 +1,11 @@ //// [tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny.ts] //// -//// [index.js] +//// [package.json] // This tests that `--noImplicitAny` disables untyped modules. +{ "name": "foo", "version": "1.2.3" } + +//// [index.js] This file is not processed. //// [a.ts] diff --git a/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.errors.txt b/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.errors.txt new file mode 100644 index 00000000000..1aeb8489b8d --- /dev/null +++ b/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.errors.txt @@ -0,0 +1,16 @@ +/a.ts(1,22): error TS7016: Could not find a declaration file for module './node_modules/foo'. '/node_modules/foo/index.js' implicitly has an 'any' type. + Try `npm install @types/foo` if it exists or add a new declaration (.d.ts) file containing `declare module 'foo';` + + +==== /a.ts (1 errors) ==== + import * as foo from "./node_modules/foo"; + ~~~~~~~~~~~~~~~~~~~~ +!!! error TS7016: Could not find a declaration file for module './node_modules/foo'. '/node_modules/foo/index.js' implicitly has an 'any' type. +!!! error TS7016: Try `npm install @types/foo` if it exists or add a new declaration (.d.ts) file containing `declare module 'foo';` + +==== /node_modules/foo/package.json (0 errors) ==== + { "name": "foo", "version": "1.2.3" } + +==== /node_modules/foo/index.js (0 errors) ==== + This file is not processed. + \ No newline at end of file diff --git a/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.js b/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.js new file mode 100644 index 00000000000..7e8c52a3bfb --- /dev/null +++ b/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.js @@ -0,0 +1,15 @@ +//// [tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny_relativePath.ts] //// + +//// [package.json] +{ "name": "foo", "version": "1.2.3" } + +//// [index.js] +This file is not processed. + +//// [a.ts] +import * as foo from "./node_modules/foo"; + + +//// [a.js] +"use strict"; +exports.__esModule = true; diff --git a/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.symbols b/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.symbols new file mode 100644 index 00000000000..0b9aff6bbef --- /dev/null +++ b/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.symbols @@ -0,0 +1,4 @@ +=== /a.ts === +import * as foo from "./node_modules/foo"; +>foo : Symbol(foo, Decl(a.ts, 0, 6)) + diff --git a/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.types b/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.types new file mode 100644 index 00000000000..d9b7333550d --- /dev/null +++ b/tests/baselines/reference/untypedModuleImport_noImplicitAny_relativePath.types @@ -0,0 +1,4 @@ +=== /a.ts === +import * as foo from "./node_modules/foo"; +>foo : any + diff --git a/tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny.ts b/tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny.ts index 1aee7c069de..39208624a72 100644 --- a/tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny.ts +++ b/tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny.ts @@ -1,8 +1,10 @@ // @noImplicitReferences: true -// @currentDirectory: / // @noImplicitAny: true // This tests that `--noImplicitAny` disables untyped modules. +// @filename: /node_modules/foo/package.json +{ "name": "foo", "version": "1.2.3" } + // @filename: /node_modules/foo/index.js This file is not processed. diff --git a/tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny_relativePath.ts b/tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny_relativePath.ts new file mode 100644 index 00000000000..ae38be5dfa0 --- /dev/null +++ b/tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny_relativePath.ts @@ -0,0 +1,11 @@ +// @noImplicitReferences: true +// @noImplicitAny: true + +// @filename: /node_modules/foo/package.json +{ "name": "foo", "version": "1.2.3" } + +// @filename: /node_modules/foo/index.js +This file is not processed. + +// @filename: /a.ts +import * as foo from "./node_modules/foo"; From 1dcc83e6d2dbc1c2f97ce5b0ce96a7c160528b3d Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 17 Jan 2018 14:29:19 -0800 Subject: [PATCH 122/172] Minor cleanup in getDynamicIndentation (#21240) --- src/services/formatting/formatting.ts | 94 ++++++++++++--------------- 1 file changed, 40 insertions(+), 54 deletions(-) diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index c020e638692..81bcd2c7137 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -537,70 +537,56 @@ namespace ts.formatting { case SyntaxKind.CloseBraceToken: case SyntaxKind.CloseBracketToken: case SyntaxKind.CloseParenToken: - return indentation + getEffectiveDelta(delta, container); + return indentation + getDelta(container); } return tokenIndentation !== Constants.Unknown ? tokenIndentation : indentation; }, - getIndentationForToken: (line, kind, container) => { - if (nodeStartLine !== line && node.decorators) { - if (kind === getFirstNonDecoratorTokenOfNode(node)) { - // if this token is the first token following the list of decorators, we do not need to indent - return indentation; - } - } - switch (kind) { - // open and close brace, 'else' and 'while' (in do statement) tokens has indentation of the parent - case SyntaxKind.OpenBraceToken: - case SyntaxKind.CloseBraceToken: - case SyntaxKind.OpenParenToken: - case SyntaxKind.CloseParenToken: - case SyntaxKind.ElseKeyword: - case SyntaxKind.WhileKeyword: - case SyntaxKind.AtToken: - return indentation; - case SyntaxKind.SlashToken: - case SyntaxKind.GreaterThanToken: { - if (container.kind === SyntaxKind.JsxOpeningElement || - container.kind === SyntaxKind.JsxClosingElement || - container.kind === SyntaxKind.JsxSelfClosingElement - ) { - return indentation; - } - break; - } - case SyntaxKind.OpenBracketToken: - case SyntaxKind.CloseBracketToken: { - if (container.kind !== SyntaxKind.MappedType) { - return indentation; - } - break; - } - } - // if token line equals to the line of containing node (this is a first token in the node) - use node indentation - return nodeStartLine !== line ? indentation + getEffectiveDelta(delta, container) : indentation; - }, + getIndentationForToken: (line, kind, container) => + shouldAddDelta(line, kind, container) ? indentation + getDelta(container) : indentation, getIndentation: () => indentation, - getDelta: child => getEffectiveDelta(delta, child), + getDelta, recomputeIndentation: lineAdded => { if (node.parent && SmartIndenter.shouldIndentChildNode(node.parent, node)) { - if (lineAdded) { - indentation += options.indentSize; - } - else { - indentation -= options.indentSize; - } - - if (SmartIndenter.shouldIndentChildNode(node)) { - delta = options.indentSize; - } - else { - delta = 0; - } + indentation += lineAdded ? options.indentSize : -options.indentSize; + delta = SmartIndenter.shouldIndentChildNode(node) ? options.indentSize : 0; } } }; - function getEffectiveDelta(delta: number, child: TextRangeWithKind) { + function shouldAddDelta(line: number, kind: SyntaxKind, container: Node): boolean { + switch (kind) { + // open and close brace, 'else' and 'while' (in do statement) tokens has indentation of the parent + case SyntaxKind.OpenBraceToken: + case SyntaxKind.CloseBraceToken: + case SyntaxKind.OpenParenToken: + case SyntaxKind.CloseParenToken: + case SyntaxKind.ElseKeyword: + case SyntaxKind.WhileKeyword: + case SyntaxKind.AtToken: + return false; + case SyntaxKind.SlashToken: + case SyntaxKind.GreaterThanToken: + switch (container.kind) { + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxClosingElement: + case SyntaxKind.JsxSelfClosingElement: + return false; + } + break; + case SyntaxKind.OpenBracketToken: + case SyntaxKind.CloseBracketToken: + if (container.kind !== SyntaxKind.MappedType) { + return false; + } + break; + } + // if token line equals to the line of containing node (this is a first token in the node) - use node indentation + return nodeStartLine !== line + // if this token is the first token following the list of decorators, we do not need to indent + && !(node.decorators && kind === getFirstNonDecoratorTokenOfNode(node)); + } + + function getDelta(child: TextRangeWithKind) { // Delta value should be zero when the node explicitly prevents indentation of the child node return SmartIndenter.nodeWillIndentChild(node, child, /*indentByDefault*/ true) ? delta : 0; } From 8281c7a137348f9e7392d35327283234ff4791a8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 17 Jan 2018 14:51:31 -0800 Subject: [PATCH 123/172] Fix the invalid file/directory location when getting file system entry for caching read directory results Fixes #20607 --- src/compiler/core.ts | 7 ++++- src/compiler/sys.ts | 2 +- src/compiler/utilities.ts | 1 - .../unittests/tsserverProjectSystem.ts | 26 +++++++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index d92a02661e8..5fa9a544f8f 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -20,6 +20,7 @@ namespace ts { /* @internal */ namespace ts { + export const emptyArray: never[] = [] as never[]; /** Create a MapLike with good performance. */ function createDictionaryObject(): MapLike { const map = Object.create(/*prototype*/ null); // tslint:disable-line:no-null-keyword @@ -3069,6 +3070,10 @@ namespace ts { readonly directories: string[]; } + export const emptyFileSystemEntries: FileSystemEntries = { + files: emptyArray, + directories: emptyArray + }; export function createCachedDirectoryStructureHost(host: DirectoryStructureHost): CachedDirectoryStructureHost { const cachedReadDirectoryResult = createMap(); const getCurrentDirectory = memoize(() => host.getCurrentDirectory()); @@ -3210,7 +3215,7 @@ namespace ts { if (path === rootDirPath) { return result; } - return getCachedFileSystemEntries(path) || createCachedFileSystemEntries(dir, path); + return tryReadDirectory(dir, path) || emptyFileSystemEntries; } } diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 841dba8a528..5293657a646 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -398,7 +398,7 @@ namespace ts { return { files, directories }; } catch (e) { - return { files: [], directories: [] }; + return emptyFileSystemEntries; } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index ffcdf42c67e..75083e159c5 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2,7 +2,6 @@ /* @internal */ namespace ts { - export const emptyArray: never[] = [] as never[]; export const resolvingEmptyArray: never[] = [] as never[]; export const emptyMap: ReadonlyMap = createMap(); export const emptyUnderscoreEscapedMap: ReadonlyUnderscoreEscapedMap = emptyMap as ReadonlyUnderscoreEscapedMap; diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 68fbc1ecba0..2ac0945587a 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -4168,6 +4168,32 @@ namespace ts.projectSystem { // Since no file from the configured project is open, it would be closed immediately projectService.checkNumberOfProjects({ configuredProjects: 0, inferredProjects: 1 }); }); + + it("should tolerate invalid include files that start in subDirectory", () => { + const projectFolder = "/user/username/projects/myproject"; + const f = { + path: `${projectFolder}/src/server/index.ts`, + content: "let x = 1" + }; + const config = { + path: `${projectFolder}/src/server/tsconfig.json`, + content: JSON.stringify({ + compiler: { + module: "commonjs", + outDir: "../../build" + }, + include: [ + "../src/**/*.ts" + ] + }) + }; + const host = createServerHost([f, config, libFile], { useCaseSensitiveFileNames: true }); + const projectService = createProjectService(host); + + projectService.openClientFile(f.path); + // Since no file from the configured project is open, it would be closed immediately + projectService.checkNumberOfProjects({ configuredProjects: 0, inferredProjects: 1 }); + }); }); describe("tsserverProjectSystem reload", () => { From e354754b2a30ab9ff558a029fb869a30668dd0ae Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 10 Jan 2018 18:28:47 -0800 Subject: [PATCH 124/172] Special case arrow functions with only parameter unused Fixes GH #18274 --- src/services/codefixes/fixUnusedIdentifier.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index b7d948b3deb..bcfcd6d1e6d 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -121,9 +121,20 @@ namespace ts.codefix { break; case SyntaxKind.Parameter: - const functionDeclaration = parent.parent; - if (functionDeclaration.parameters.length === 1) { - changes.deleteNode(sourceFile, parent); + const oldFunction = parent.parent; + if (isArrowFunction(oldFunction) && oldFunction.parameters.length === 1) { + const newFunction = updateArrowFunction( + oldFunction, + oldFunction.modifiers, + oldFunction.typeParameters, + /*parameters*/ undefined, + oldFunction.type, + oldFunction.equalsGreaterThanToken, + oldFunction.body); + + suppressLeadingAndTrailingTrivia(newFunction); + + changes.replaceRange(sourceFile, { pos: oldFunction.getStart(), end: oldFunction.end }, newFunction); } else { changes.deleteNodeInList(sourceFile, parent); From 3b5689fa1fdf036f90c6e836681f312356b66aa5 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Thu, 11 Jan 2018 10:51:55 -0800 Subject: [PATCH 125/172] Add more test coverage for unusedParameterInLambda --- tests/cases/fourslash/unusedParameterInLambda1.ts | 5 +++-- tests/cases/fourslash/unusedParameterInLambda2.ts | 14 ++++++++++++++ tests/cases/fourslash/unusedParameterInLambda3.ts | 14 ++++++++++++++ tests/cases/fourslash/unusedParameterInLambda4.ts | 13 +++++++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 tests/cases/fourslash/unusedParameterInLambda2.ts create mode 100644 tests/cases/fourslash/unusedParameterInLambda3.ts create mode 100644 tests/cases/fourslash/unusedParameterInLambda4.ts diff --git a/tests/cases/fourslash/unusedParameterInLambda1.ts b/tests/cases/fourslash/unusedParameterInLambda1.ts index ace79adae46..88a0e8c9272 100644 --- a/tests/cases/fourslash/unusedParameterInLambda1.ts +++ b/tests/cases/fourslash/unusedParameterInLambda1.ts @@ -3,11 +3,12 @@ // @noUnusedLocals: true // @noUnusedParameters: true //// function f1() { -//// [|return (x:number) => {}|] +//// [|return /*~a*/(/*~b*/x/*~c*/:/*~d*/number/*~e*/)/*~f*/ => /*~g*/{/*~h*/}/*~i*/|] //// } +// In a perfect world, /*~f*/ and /*~h*/ would probably be retained. verify.codeFix({ description: "Remove declaration for: 'x'", index: 0, - newRangeContent: "return () => {}", + newRangeContent: "return /*~a*/() => /*~g*/ { }/*~i*/", }); diff --git a/tests/cases/fourslash/unusedParameterInLambda2.ts b/tests/cases/fourslash/unusedParameterInLambda2.ts new file mode 100644 index 00000000000..cc46288fe3c --- /dev/null +++ b/tests/cases/fourslash/unusedParameterInLambda2.ts @@ -0,0 +1,14 @@ +/// + +// @noUnusedLocals: true +// @noUnusedParameters: true +//// function f1() { +//// [|return /*~a*/x/*~b*/ /*~c*/=>/*~d*/ {/*~e*/}/*~f*/|] +//// } + +// In a perfect world, /*~c*/ and /*~e*/ would probably be retained. +verify.codeFix({ + description: "Remove declaration for: 'x'", + index: 0, + newRangeContent: "return /*~a*/() => /*~d*/ { }/*~f*/", +}); diff --git a/tests/cases/fourslash/unusedParameterInLambda3.ts b/tests/cases/fourslash/unusedParameterInLambda3.ts new file mode 100644 index 00000000000..e5f1930eb9d --- /dev/null +++ b/tests/cases/fourslash/unusedParameterInLambda3.ts @@ -0,0 +1,14 @@ +/// + +// @noUnusedLocals: true +// @noUnusedParameters: true +//// function f1() { +//// [|return /*~a*/(/*~b*/x/*~c*/,/*~d*/y/*~e*/)/*~f*/ => /*~g*/x/*~h*/|] +//// } + +// In a perfect world, /*~c*/ would probably be retained, rather than /*~e*/. +verify.codeFix({ + description: "Remove declaration for: 'y'", + index: 0, + newRangeContent: "return /*~a*/(/*~b*/x/*~e*/)/*~f*/ => /*~g*/x/*~h*/", +}); diff --git a/tests/cases/fourslash/unusedParameterInLambda4.ts b/tests/cases/fourslash/unusedParameterInLambda4.ts new file mode 100644 index 00000000000..c2273ebaad0 --- /dev/null +++ b/tests/cases/fourslash/unusedParameterInLambda4.ts @@ -0,0 +1,13 @@ +/// + +// @noUnusedLocals: true +// @noUnusedParameters: true +//// function f1() { +//// [|return /*~a*/(/*~b*/x/*~c*/,/*~d*/y/*~e*/)/*~f*/ => /*~g*/y/*~h*/|] +//// } + +verify.codeFix({ + description: "Remove declaration for: 'x'", + index: 0, + newRangeContent: "return /*~a*/(/*~d*/y/*~e*/)/*~f*/ => /*~g*/y/*~h*/", +}); From 5de6ac1a2f81362578ad338d0d88e3d040d3f7bc Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Fri, 12 Jan 2018 11:11:01 -0800 Subject: [PATCH 126/172] Simplify test cases --- src/services/codefixes/fixUnusedIdentifier.ts | 3 +++ tests/cases/fourslash/unusedParameterInLambda1.ts | 6 ++---- tests/cases/fourslash/unusedParameterInLambda2.ts | 6 ++---- tests/cases/fourslash/unusedParameterInLambda3.ts | 6 ++---- tests/cases/fourslash/unusedParameterInLambda4.ts | 6 ++---- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index bcfcd6d1e6d..69778b543a0 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -123,6 +123,9 @@ namespace ts.codefix { case SyntaxKind.Parameter: const oldFunction = parent.parent; if (isArrowFunction(oldFunction) && oldFunction.parameters.length === 1) { + // Lambdas with exactly one parameter are special because, after removal, there + // must be an empty parameter list (i.e. `()`) and this won't necessarily be the + // case if the parameter is simply removed (e.g. in `x => 1`). const newFunction = updateArrowFunction( oldFunction, oldFunction.modifiers, diff --git a/tests/cases/fourslash/unusedParameterInLambda1.ts b/tests/cases/fourslash/unusedParameterInLambda1.ts index 88a0e8c9272..fdb53a8a05f 100644 --- a/tests/cases/fourslash/unusedParameterInLambda1.ts +++ b/tests/cases/fourslash/unusedParameterInLambda1.ts @@ -2,13 +2,11 @@ // @noUnusedLocals: true // @noUnusedParameters: true -//// function f1() { -//// [|return /*~a*/(/*~b*/x/*~c*/:/*~d*/number/*~e*/)/*~f*/ => /*~g*/{/*~h*/}/*~i*/|] -//// } +////[|/*~a*/(/*~b*/x/*~c*/:/*~d*/number/*~e*/)/*~f*/ => /*~g*/{/*~h*/}/*~i*/|] // In a perfect world, /*~f*/ and /*~h*/ would probably be retained. verify.codeFix({ description: "Remove declaration for: 'x'", index: 0, - newRangeContent: "return /*~a*/() => /*~g*/ { }/*~i*/", + newRangeContent: "/*~a*/() => /*~g*/ { }/*~i*/", }); diff --git a/tests/cases/fourslash/unusedParameterInLambda2.ts b/tests/cases/fourslash/unusedParameterInLambda2.ts index cc46288fe3c..e2b1be346b8 100644 --- a/tests/cases/fourslash/unusedParameterInLambda2.ts +++ b/tests/cases/fourslash/unusedParameterInLambda2.ts @@ -2,13 +2,11 @@ // @noUnusedLocals: true // @noUnusedParameters: true -//// function f1() { -//// [|return /*~a*/x/*~b*/ /*~c*/=>/*~d*/ {/*~e*/}/*~f*/|] -//// } +////[|/*~a*/x/*~b*/ /*~c*/=>/*~d*/ {/*~e*/}/*~f*/|] // In a perfect world, /*~c*/ and /*~e*/ would probably be retained. verify.codeFix({ description: "Remove declaration for: 'x'", index: 0, - newRangeContent: "return /*~a*/() => /*~d*/ { }/*~f*/", + newRangeContent: "/*~a*/() => /*~d*/ { }/*~f*/", }); diff --git a/tests/cases/fourslash/unusedParameterInLambda3.ts b/tests/cases/fourslash/unusedParameterInLambda3.ts index e5f1930eb9d..f95d142d436 100644 --- a/tests/cases/fourslash/unusedParameterInLambda3.ts +++ b/tests/cases/fourslash/unusedParameterInLambda3.ts @@ -2,13 +2,11 @@ // @noUnusedLocals: true // @noUnusedParameters: true -//// function f1() { -//// [|return /*~a*/(/*~b*/x/*~c*/,/*~d*/y/*~e*/)/*~f*/ => /*~g*/x/*~h*/|] -//// } +////[|/*~a*/(/*~b*/x/*~c*/,/*~d*/y/*~e*/)/*~f*/ => /*~g*/x/*~h*/|] // In a perfect world, /*~c*/ would probably be retained, rather than /*~e*/. verify.codeFix({ description: "Remove declaration for: 'y'", index: 0, - newRangeContent: "return /*~a*/(/*~b*/x/*~e*/)/*~f*/ => /*~g*/x/*~h*/", + newRangeContent: "/*~a*/(/*~b*/x/*~e*/)/*~f*/ => /*~g*/x/*~h*/", }); diff --git a/tests/cases/fourslash/unusedParameterInLambda4.ts b/tests/cases/fourslash/unusedParameterInLambda4.ts index c2273ebaad0..015f081891a 100644 --- a/tests/cases/fourslash/unusedParameterInLambda4.ts +++ b/tests/cases/fourslash/unusedParameterInLambda4.ts @@ -2,12 +2,10 @@ // @noUnusedLocals: true // @noUnusedParameters: true -//// function f1() { -//// [|return /*~a*/(/*~b*/x/*~c*/,/*~d*/y/*~e*/)/*~f*/ => /*~g*/y/*~h*/|] -//// } +////[|/*~a*/(/*~b*/x/*~c*/,/*~d*/y/*~e*/)/*~f*/ => /*~g*/y/*~h*/|] verify.codeFix({ description: "Remove declaration for: 'x'", index: 0, - newRangeContent: "return /*~a*/(/*~d*/y/*~e*/)/*~f*/ => /*~g*/y/*~h*/", + newRangeContent: "/*~a*/(/*~d*/y/*~e*/)/*~f*/ => /*~g*/y/*~h*/", }); From 9a83077d78cadc781284f001c45e0fcf808d6a39 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 17 Jan 2018 15:12:39 -0800 Subject: [PATCH 127/172] Add explanatory comment --- src/services/codefixes/fixUnusedIdentifier.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index 69778b543a0..59d19a1bd9b 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -135,6 +135,9 @@ namespace ts.codefix { oldFunction.equalsGreaterThanToken, oldFunction.body); + // Drop leading and trailing trivia of the new function because we're only going + // to replace the span (vs the full span) of the old function - the old leading + // and trailing trivia will remain. suppressLeadingAndTrailingTrivia(newFunction); changes.replaceRange(sourceFile, { pos: oldFunction.getStart(), end: oldFunction.end }, newFunction); From b4a382bdd2a9cb25555e1f852b9e759372bc996f Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Thu, 11 Jan 2018 14:53:31 -0800 Subject: [PATCH 128/172] Stop explicitly storing newline in refactoring/code fix contexts It's already in the EditorSettings and the LanguageServiceHost. Fixes #18291 Fixes #18445 --- src/harness/unittests/extractTestHelpers.ts | 2 -- src/services/codeFixProvider.ts | 5 ++-- .../addMissingInvocationForDecorator.ts | 2 +- ...correctQualifiedNameToIndexedAccessType.ts | 2 +- .../codefixes/disableJsDiagnostics.ts | 6 +++-- src/services/codefixes/fixAddMissingMember.ts | 14 +++++----- .../codefixes/fixAwaitInSyncFunction.ts | 2 +- ...sDoesntImplementInheritedAbstractMember.ts | 2 +- .../fixClassIncorrectlyImplementsInterface.ts | 2 +- .../fixClassSuperMustPrecedeThisAccess.ts | 2 +- .../fixConstructorForDerivedNeedSuperCall.ts | 2 +- .../fixExtendsInterfaceBecomesImplements.ts | 2 +- .../fixForgottenThisPropertyAccess.ts | 2 +- .../codefixes/fixInvalidImportSyntax.ts | 6 ++--- src/services/codefixes/fixSpelling.ts | 2 +- src/services/codefixes/fixUnusedIdentifier.ts | 4 +-- src/services/codefixes/importFixes.ts | 27 +++++++++---------- src/services/completions.ts | 1 - src/services/refactorProvider.ts | 20 ++++++++++++-- .../refactors/annotateWithTypeFromJSDoc.ts | 4 +-- .../refactors/convertFunctionToEs6Class.ts | 2 +- src/services/refactors/convertToEs6Module.ts | 2 +- src/services/refactors/extractSymbol.ts | 4 +-- src/services/refactors/useDefaultImport.ts | 2 +- src/services/services.ts | 7 ++--- 25 files changed, 67 insertions(+), 59 deletions(-) diff --git a/src/harness/unittests/extractTestHelpers.ts b/src/harness/unittests/extractTestHelpers.ts index a04f443c3c9..f519555bd26 100644 --- a/src/harness/unittests/extractTestHelpers.ts +++ b/src/harness/unittests/extractTestHelpers.ts @@ -121,7 +121,6 @@ namespace ts { const sourceFile = program.getSourceFile(path); const context: RefactorContext = { cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse }, - newLineCharacter, program, file: sourceFile, startPosition: selectionRange.start, @@ -185,7 +184,6 @@ namespace ts { const sourceFile = program.getSourceFile(f.path); const context: RefactorContext = { cancellationToken: { throwIfCancellationRequested: noop, isCancellationRequested: returnFalse }, - newLineCharacter, program, file: sourceFile, startPosition: selectionRange.start, diff --git a/src/services/codeFixProvider.ts b/src/services/codeFixProvider.ts index aa1f2fb6885..619bcc8813c 100644 --- a/src/services/codeFixProvider.ts +++ b/src/services/codeFixProvider.ts @@ -7,10 +7,9 @@ namespace ts { getAllCodeActions?(context: CodeFixAllContext): CombinedCodeActions; } - export interface CodeFixContextBase extends textChanges.TextChangesContext { + export interface CodeFixContextBase extends RefactorOrCodeFixContext { sourceFile: SourceFile; program: Program; - host: LanguageServiceHost; cancellationToken: CancellationToken; } @@ -84,7 +83,7 @@ namespace ts { export function codeFixAll(context: CodeFixAllContext, errorCodes: number[], use: (changes: textChanges.ChangeTracker, error: Diagnostic, commands: Push) => void): CombinedCodeActions { const commands: CodeActionCommand[] = []; - const changes = textChanges.ChangeTracker.with(context, t => + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => eachDiagnostic(context, errorCodes, diag => use(t, diag, commands))); return createCombinedCodeActions(changes, commands.length === 0 ? undefined : commands); } diff --git a/src/services/codefixes/addMissingInvocationForDecorator.ts b/src/services/codefixes/addMissingInvocationForDecorator.ts index d063df87be4..25214f08218 100644 --- a/src/services/codefixes/addMissingInvocationForDecorator.ts +++ b/src/services/codefixes/addMissingInvocationForDecorator.ts @@ -5,7 +5,7 @@ namespace ts.codefix { registerCodeFix({ errorCodes, getCodeActions: (context) => { - const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span.start)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => makeChange(t, context.sourceFile, context.span.start)); return [{ description: getLocaleSpecificMessage(Diagnostics.Call_decorator_expression), changes, fixId }]; }, fixIds: [fixId], diff --git a/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts b/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts index de4498035be..076ea6135ae 100644 --- a/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts +++ b/src/services/codefixes/correctQualifiedNameToIndexedAccessType.ts @@ -7,7 +7,7 @@ namespace ts.codefix { getCodeActions(context) { const qualifiedName = getQualifiedName(context.sourceFile, context.span.start); if (!qualifiedName) return undefined; - const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, qualifiedName)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChange(t, context.sourceFile, qualifiedName)); const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Rewrite_as_the_indexed_access_type_0), [`${qualifiedName.left.text}["${qualifiedName.right.text}"]`]); return [{ description, changes, fixId }]; }, diff --git a/src/services/codefixes/disableJsDiagnostics.ts b/src/services/codefixes/disableJsDiagnostics.ts index 6a59e61d52d..cf006a6066c 100644 --- a/src/services/codefixes/disableJsDiagnostics.ts +++ b/src/services/codefixes/disableJsDiagnostics.ts @@ -9,12 +9,14 @@ namespace ts.codefix { registerCodeFix({ errorCodes, getCodeActions(context) { - const { sourceFile, program, newLineCharacter, span } = context; + const { sourceFile, program, span } = context; if (!isInJavaScriptFile(sourceFile) || !isCheckJsEnabledForFile(sourceFile, program.getCompilerOptions())) { return undefined; } + const newLineCharacter = getNewLineFromContext(context); + return [{ description: getLocaleSpecificMessage(Diagnostics.Ignore_this_error_message), changes: [createFileTextChanges(sourceFile.fileName, [getIgnoreCommentLocationForLocation(sourceFile, span.start, newLineCharacter)])], @@ -36,7 +38,7 @@ namespace ts.codefix { fixIds: [fixId], // No point applying as a group, doing it once will fix all errors getAllCodeActions: context => codeFixAllWithTextChanges(context, errorCodes, (changes, err) => { if (err.start !== undefined) { - changes.push(getIgnoreCommentLocationForLocation(err.file!, err.start, context.newLineCharacter)); + changes.push(getIgnoreCommentLocationForLocation(err.file!, err.start, getNewLineFromContext(context))); } }), }); diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 0fc6a430e19..8e45efa09ad 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -96,7 +96,7 @@ namespace ts.codefix { } function getActionsForAddMissingMemberInJavaScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFixAction | undefined { - const changes = textChanges.ChangeTracker.with(context, t => addMissingMemberInJs(t, classDeclarationSourceFile, classDeclaration, tokenName, makeStatic)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => addMissingMemberInJs(t, classDeclarationSourceFile, classDeclaration, tokenName, makeStatic)); if (changes.length === 0) return undefined; const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Initialize_static_property_0 : Diagnostics.Initialize_property_0_in_the_constructor), [tokenName]); return { description, changes, fixId }; @@ -142,9 +142,9 @@ namespace ts.codefix { return typeNode || createKeywordTypeNode(SyntaxKind.AnyKeyword); } - function createAddPropertyDeclarationAction(context: textChanges.TextChangesContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, makeStatic: boolean, tokenName: string, typeNode: TypeNode): CodeFixAction { + function createAddPropertyDeclarationAction(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, makeStatic: boolean, tokenName: string, typeNode: TypeNode): CodeFixAction { const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0), [tokenName]); - const changes = textChanges.ChangeTracker.with(context, t => addPropertyDeclaration(t, classDeclarationSourceFile, classDeclaration, tokenName, typeNode, makeStatic)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => addPropertyDeclaration(t, classDeclarationSourceFile, classDeclaration, tokenName, typeNode, makeStatic)); return { description, changes, fixId }; } @@ -159,7 +159,7 @@ namespace ts.codefix { changeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, property); } - function createAddIndexSignatureAction(context: textChanges.TextChangesContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, typeNode: TypeNode): CodeFixAction { + function createAddIndexSignatureAction(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, typeNode: TypeNode): CodeFixAction { // Index signatures cannot have the static modifier. const stringTypeNode = createKeywordTypeNode(SyntaxKind.StringKeyword); const indexingParameter = createParameter( @@ -176,14 +176,14 @@ namespace ts.codefix { [indexingParameter], typeNode); - const changes = textChanges.ChangeTracker.with(context, t => t.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, indexSignature)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => t.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, indexSignature)); // No fixId here because code-fix-all currently only works on adding individual named properties. return { description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]), changes, fixId: undefined }; } - function getActionForMethodDeclaration(context: textChanges.TextChangesContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean): CodeFixAction | undefined { + function getActionForMethodDeclaration(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, callExpression: CallExpression, makeStatic: boolean, inJs: boolean): CodeFixAction | undefined { const description = formatStringFromArgs(getLocaleSpecificMessage(makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0), [token.text]); - const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(t, classDeclarationSourceFile, classDeclaration, token, callExpression, makeStatic, inJs)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => addMethodDeclaration(t, classDeclarationSourceFile, classDeclaration, token, callExpression, makeStatic, inJs)); return { description, changes, fixId }; } diff --git a/src/services/codefixes/fixAwaitInSyncFunction.ts b/src/services/codefixes/fixAwaitInSyncFunction.ts index 883993e7b51..7d09e5f214f 100644 --- a/src/services/codefixes/fixAwaitInSyncFunction.ts +++ b/src/services/codefixes/fixAwaitInSyncFunction.ts @@ -11,7 +11,7 @@ namespace ts.codefix { const { sourceFile, span } = context; const nodes = getNodes(sourceFile, span.start); if (!nodes) return undefined; - const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, nodes)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChange(t, sourceFile, nodes)); return [{ description: getLocaleSpecificMessage(Diagnostics.Add_async_modifier_to_containing_function), changes, fixId }]; }, fixIds: [fixId], diff --git a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts index da3685339ab..6e215666ddf 100644 --- a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts +++ b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts @@ -9,7 +9,7 @@ namespace ts.codefix { errorCodes, getCodeActions(context) { const { program, sourceFile, span } = context; - const changes = textChanges.ChangeTracker.with(context, t => + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => addMissingMembers(getClass(sourceFile, span.start), sourceFile, program.getTypeChecker(), t)); return changes.length === 0 ? undefined : [{ description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class), changes, fixId }]; }, diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index 0236b0c79b7..1eb5bfac986 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -10,7 +10,7 @@ namespace ts.codefix { const classDeclaration = getClass(sourceFile, span.start); const checker = program.getTypeChecker(); return mapDefined(getClassImplementsHeritageClauseElements(classDeclaration), implementedTypeNode => { - const changes = textChanges.ChangeTracker.with(context, t => addMissingDeclarations(checker, implementedTypeNode, sourceFile, classDeclaration, t)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => addMissingDeclarations(checker, implementedTypeNode, sourceFile, classDeclaration, t)); if (changes.length === 0) return undefined; const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Implement_interface_0), [implementedTypeNode.getText()]); return { description, changes, fixId }; diff --git a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts index 1595bbf3c13..76c1c588097 100644 --- a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts +++ b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts @@ -9,7 +9,7 @@ namespace ts.codefix { const nodes = getNodes(sourceFile, span.start); if (!nodes) return undefined; const { constructor, superCall } = nodes; - const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, constructor, superCall)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChange(t, sourceFile, constructor, superCall)); return [{ description: getLocaleSpecificMessage(Diagnostics.Make_super_call_the_first_statement_in_the_constructor), changes, fixId }]; }, fixIds: [fixId], diff --git a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts index 8fe2e8458a8..0b747344e3b 100644 --- a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts +++ b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts @@ -7,7 +7,7 @@ namespace ts.codefix { getCodeActions(context) { const { sourceFile, span } = context; const ctr = getNode(sourceFile, span.start); - const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, ctr)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChange(t, sourceFile, ctr)); return [{ description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call), changes, fixId }]; }, fixIds: [fixId], diff --git a/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts b/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts index d98ca556f47..a4e91410db5 100644 --- a/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts +++ b/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts @@ -9,7 +9,7 @@ namespace ts.codefix { const nodes = getNodes(sourceFile, context.span.start); if (!nodes) return undefined; const { extendsToken, heritageClauses } = nodes; - const changes = textChanges.ChangeTracker.with(context, t => doChanges(t, sourceFile, extendsToken, heritageClauses)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChanges(t, sourceFile, extendsToken, heritageClauses)); return [{ description: getLocaleSpecificMessage(Diagnostics.Change_extends_to_implements), changes, fixId }]; }, fixIds: [fixId], diff --git a/src/services/codefixes/fixForgottenThisPropertyAccess.ts b/src/services/codefixes/fixForgottenThisPropertyAccess.ts index 19610da0b15..8c337c16c4f 100644 --- a/src/services/codefixes/fixForgottenThisPropertyAccess.ts +++ b/src/services/codefixes/fixForgottenThisPropertyAccess.ts @@ -7,7 +7,7 @@ namespace ts.codefix { getCodeActions(context) { const { sourceFile } = context; const token = getNode(sourceFile, context.span.start); - const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, token)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChange(t, sourceFile, token)); return [{ description: getLocaleSpecificMessage(Diagnostics.Add_this_to_unresolved_variable), changes, fixId }]; }, fixIds: [fixId], diff --git a/src/services/codefixes/fixInvalidImportSyntax.ts b/src/services/codefixes/fixInvalidImportSyntax.ts index f98f1eaea7c..0bfc6a6090d 100644 --- a/src/services/codefixes/fixInvalidImportSyntax.ts +++ b/src/services/codefixes/fixInvalidImportSyntax.ts @@ -32,7 +32,7 @@ namespace ts.codefix { createImportClause(namespace.name, /*namedBindings*/ undefined), node.moduleSpecifier ); - const changeTracker = textChanges.ChangeTracker.fromContext(context); + const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context)); changeTracker.replaceNode(sourceFile, node, replacement, { useNonAdjustedEndPosition: true }); const changes = changeTracker.getChanges(); variations.push({ @@ -48,7 +48,7 @@ namespace ts.codefix { namespace.name, createExternalModuleReference(node.moduleSpecifier) ); - const changeTracker = textChanges.ChangeTracker.fromContext(context); + const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context)); changeTracker.replaceNode(sourceFile, node, replacement, { useNonAdjustedEndPosition: true }); const changes = changeTracker.getChanges(); variations.push({ @@ -86,7 +86,7 @@ namespace ts.codefix { addRange(fixes, getCodeFixesForImportDeclaration(context, relatedImport)); } const propertyAccess = createPropertyAccess(expr, "default"); - const changeTracker = textChanges.ChangeTracker.fromContext(context); + const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context)); changeTracker.replaceNode(sourceFile, expr, propertyAccess, {}); const changes = changeTracker.getChanges(); fixes.push({ diff --git a/src/services/codefixes/fixSpelling.ts b/src/services/codefixes/fixSpelling.ts index 729f14e9ef9..95159012f48 100644 --- a/src/services/codefixes/fixSpelling.ts +++ b/src/services/codefixes/fixSpelling.ts @@ -12,7 +12,7 @@ namespace ts.codefix { const info = getInfo(sourceFile, context.span.start, context.program.getTypeChecker()); if (!info) return undefined; const { node, suggestion } = info; - const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, node, suggestion)); + const changes = textChanges.ChangeTracker.with(toTextChangesContext(context), t => doChange(t, sourceFile, node, suggestion)); const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Change_spelling_to_0), [suggestion]); return [{ description, changes, fixId }]; }, diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index b7d948b3deb..93c8ae80ac9 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -13,13 +13,13 @@ namespace ts.codefix { const token = getToken(sourceFile, context.span.start); const result: CodeFixAction[] = []; - const deletion = textChanges.ChangeTracker.with(context, t => tryDeleteDeclaration(t, sourceFile, token)); + const deletion = textChanges.ChangeTracker.with(toTextChangesContext(context), t => tryDeleteDeclaration(t, sourceFile, token)); if (deletion.length) { const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Remove_declaration_for_Colon_0), [token.getText()]); result.push({ description, changes: deletion, fixId: fixIdDelete }); } - const prefix = textChanges.ChangeTracker.with(context, t => tryPrefixDeclaration(t, context.errorCode, sourceFile, token)); + const prefix = textChanges.ChangeTracker.with(toTextChangesContext(context), t => tryPrefixDeclaration(t, context.errorCode, sourceFile, token)); if (prefix.length) { const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Prefix_0_with_an_underscore), [token.getText()]); result.push({ description, changes: prefix, fixId: fixIdPrefix }); diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 8f69cd95c11..204030e3104 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -24,16 +24,14 @@ namespace ts.codefix { moduleSpecifier?: string; } - interface SymbolContext extends textChanges.TextChangesContext { + interface SymbolContext { sourceFile: SourceFile; symbolName: string; + formatContext: ts.formatting.FormatContext; } - interface SymbolAndTokenContext extends SymbolContext { + interface ImportCodeFixContext extends SymbolContext { symbolToken: Identifier | undefined; - } - - interface ImportCodeFixContext extends SymbolAndTokenContext { host: LanguageServiceHost; program: Program; checker: TypeChecker; @@ -173,7 +171,6 @@ namespace ts.codefix { const symbolToken = cast(getTokenAtPosition(context.sourceFile, context.span.start, /*includeJsDocComment*/ false), isIdentifier); return { host: context.host, - newLineCharacter: context.newLineCharacter, formatContext: context.formatContext, sourceFile: context.sourceFile, program, @@ -260,7 +257,7 @@ namespace ts.codefix { } } - function getCodeActionForNewImport(context: SymbolContext & { kind: ImportKind }, moduleSpecifier: string): ImportCodeAction { + function getCodeActionForNewImport(context: SymbolContext & RefactorOrCodeFixContext & { kind: ImportKind }, moduleSpecifier: string): ImportCodeAction { const { kind, sourceFile, symbolName } = context; const lastImportDeclaration = findLast(sourceFile.statements, isAnyImportSyntax); @@ -278,7 +275,7 @@ namespace ts.codefix { createIdentifier(symbolName), createExternalModuleReference(quotedModuleSpecifier)); - const changes = ChangeTracker.with(context, changeTracker => { + const changes = ChangeTracker.with(toTextChangesContext(context), changeTracker => { if (lastImportDeclaration) { changeTracker.insertNodeAfter(sourceFile, lastImportDeclaration, importDecl); } @@ -672,33 +669,33 @@ namespace ts.codefix { return expression && isStringLiteral(expression) ? expression.text : undefined; } - function tryUpdateExistingImport(context: SymbolContext & { kind: ImportKind }, importClause: ImportClause | ImportEqualsDeclaration): FileTextChanges[] | undefined { + function tryUpdateExistingImport(context: SymbolContext & RefactorOrCodeFixContext & { kind: ImportKind }, importClause: ImportClause | ImportEqualsDeclaration): FileTextChanges[] | undefined { const { symbolName, sourceFile, kind } = context; const { name } = importClause; const { namedBindings } = importClause.kind !== SyntaxKind.ImportEqualsDeclaration && importClause; switch (kind) { case ImportKind.Default: - return name ? undefined : ChangeTracker.with(context, t => + return name ? undefined : ChangeTracker.with(toTextChangesContext(context), t => t.replaceNode(sourceFile, importClause, createImportClause(createIdentifier(symbolName), namedBindings))); case ImportKind.Named: { const newImportSpecifier = createImportSpecifier(/*propertyName*/ undefined, createIdentifier(symbolName)); if (namedBindings && namedBindings.kind === SyntaxKind.NamedImports && namedBindings.elements.length !== 0) { // There are already named imports; add another. - return ChangeTracker.with(context, t => t.insertNodeInListAfter( + return ChangeTracker.with(toTextChangesContext(context), t => t.insertNodeInListAfter( sourceFile, namedBindings.elements[namedBindings.elements.length - 1], newImportSpecifier)); } if (!namedBindings || namedBindings.kind === SyntaxKind.NamedImports && namedBindings.elements.length === 0) { - return ChangeTracker.with(context, t => + return ChangeTracker.with(toTextChangesContext(context), t => t.replaceNode(sourceFile, importClause, createImportClause(name, createNamedImports([newImportSpecifier])))); } return undefined; } case ImportKind.Namespace: - return namedBindings ? undefined : ChangeTracker.with(context, t => + return namedBindings ? undefined : ChangeTracker.with(toTextChangesContext(context), t => t.replaceNode(sourceFile, importClause, createImportClause(name, createNamespaceImport(createIdentifier(symbolName))))); case ImportKind.Equals: @@ -709,7 +706,7 @@ namespace ts.codefix { } } - function getCodeActionForUseExistingNamespaceImport(namespacePrefix: string, context: SymbolContext, symbolToken: Identifier): ImportCodeAction { + function getCodeActionForUseExistingNamespaceImport(namespacePrefix: string, context: SymbolContext & RefactorOrCodeFixContext, symbolToken: Identifier): ImportCodeAction { const { symbolName, sourceFile } = context; /** @@ -723,7 +720,7 @@ namespace ts.codefix { * become "ns.foo" */ // Prefix the node instead of it replacing it, because this may be used for import completions and we don't want the text changes to overlap with the identifier being completed. - const changes = ChangeTracker.with(context, tracker => + const changes = ChangeTracker.with(toTextChangesContext(context), tracker => tracker.changeIdentifierToPropertyAccess(sourceFile, namespacePrefix, symbolToken)); return createCodeAction(Diagnostics.Change_0_to_1, [symbolName, `${namespacePrefix}.${symbolName}`], changes, "CodeChange", /*moduleSpecifier*/ undefined); } diff --git a/src/services/completions.ts b/src/services/completions.ts index 36a10a1b578..82e7065228a 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -627,7 +627,6 @@ namespace ts.Completions { host, program, checker, - newLineCharacter: host.getNewLine(), compilerOptions, sourceFile, formatContext, diff --git a/src/services/refactorProvider.ts b/src/services/refactorProvider.ts index 85ef9113bda..3d2a60e63e9 100644 --- a/src/services/refactorProvider.ts +++ b/src/services/refactorProvider.ts @@ -14,12 +14,28 @@ namespace ts { getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined; } - export interface RefactorContext extends textChanges.TextChangesContext { + export interface RefactorOrCodeFixContext { + host: LanguageServiceHost; + formatContext: ts.formatting.FormatContext; + } + + export function getNewLineFromContext(context: RefactorOrCodeFixContext) { + const formatSettings = context.formatContext.options; + return formatSettings ? formatSettings.newLineCharacter : context.host.getNewLine(); + } + + export function toTextChangesContext(context: RefactorOrCodeFixContext): textChanges.TextChangesContext { + return { + newLineCharacter: getNewLineFromContext(context), + formatContext: context.formatContext, + }; + } + + export interface RefactorContext extends RefactorOrCodeFixContext { file: SourceFile; startPosition: number; endPosition?: number; program: Program; - host: LanguageServiceHost; cancellationToken?: CancellationToken; } diff --git a/src/services/refactors/annotateWithTypeFromJSDoc.ts b/src/services/refactors/annotateWithTypeFromJSDoc.ts index d3bf59638b2..a39cdc20c42 100644 --- a/src/services/refactors/annotateWithTypeFromJSDoc.ts +++ b/src/services/refactors/annotateWithTypeFromJSDoc.ts @@ -78,7 +78,7 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { return Debug.fail(`!decl || !jsdocType || decl.type: !${decl} || !${jsdocType} || ${decl.type}`); } - const changeTracker = textChanges.ChangeTracker.fromContext(context); + const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context)); const declarationWithType = addType(decl, transformJSDocType(jsdocType) as TypeNode); suppressLeadingAndTrailingTrivia(declarationWithType); changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, declarationWithType); @@ -93,7 +93,7 @@ namespace ts.refactor.annotateWithTypeFromJSDoc { const sourceFile = context.file; const token = getTokenAtPosition(sourceFile, context.startPosition, /*includeJsDocComment*/ false); const decl = findAncestor(token, isFunctionLikeDeclaration); - const changeTracker = textChanges.ChangeTracker.fromContext(context); + const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context)); const functionWithType = addTypesToFunctionLike(decl); suppressLeadingAndTrailingTrivia(functionWithType); changeTracker.replaceRange(sourceFile, { pos: decl.getStart(), end: decl.end }, functionWithType); diff --git a/src/services/refactors/convertFunctionToEs6Class.ts b/src/services/refactors/convertFunctionToEs6Class.ts index cddf40ae017..93e39bc683c 100644 --- a/src/services/refactors/convertFunctionToEs6Class.ts +++ b/src/services/refactors/convertFunctionToEs6Class.ts @@ -59,7 +59,7 @@ namespace ts.refactor.convertFunctionToES6Class { } const ctorDeclaration = ctorSymbol.valueDeclaration; - const changeTracker = textChanges.ChangeTracker.fromContext(context); + const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context)); let precedingNode: Node; let newClassDeclaration: ClassDeclaration; diff --git a/src/services/refactors/convertToEs6Module.ts b/src/services/refactors/convertToEs6Module.ts index 1046bf90aa6..933797554dd 100644 --- a/src/services/refactors/convertToEs6Module.ts +++ b/src/services/refactors/convertToEs6Module.ts @@ -74,7 +74,7 @@ namespace ts.refactor { Debug.assertEqual(actionName, _actionName); const { file, program } = context; Debug.assert(isSourceFileJavaScript(file)); - const edits = textChanges.ChangeTracker.with(context, changes => { + const edits = textChanges.ChangeTracker.with(toTextChangesContext(context), changes => { const moduleExportsChangedToDefault = convertFileToEs6Module(file, program.getTypeChecker(), changes, program.getCompilerOptions().target); if (moduleExportsChangedToDefault) { for (const importingFile of program.getSourceFiles()) { diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index b3110a0ca36..9a9c30756a4 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -806,7 +806,7 @@ namespace ts.refactor.extractSymbol { ); } - const changeTracker = textChanges.ChangeTracker.fromContext(context); + const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context)); const minInsertionPos = (isReadonlyArray(range.range) ? last(range.range) : range.range).end; const nodeToInsertBefore = getNodeToInsertFunctionBefore(minInsertionPos, scope); if (nodeToInsertBefore) { @@ -1011,7 +1011,7 @@ namespace ts.refactor.extractSymbol { const initializer = transformConstantInitializer(node, substitutions); suppressLeadingAndTrailingTrivia(initializer); - const changeTracker = textChanges.ChangeTracker.fromContext(context); + const changeTracker = textChanges.ChangeTracker.fromContext(toTextChangesContext(context)); if (isClassLike(scope)) { Debug.assert(!isJS); // See CannotExtractToJSClass diff --git a/src/services/refactors/useDefaultImport.ts b/src/services/refactors/useDefaultImport.ts index a103168f67b..e64eaf0bfcb 100644 --- a/src/services/refactors/useDefaultImport.ts +++ b/src/services/refactors/useDefaultImport.ts @@ -54,7 +54,7 @@ namespace ts.refactor.installTypesForPackage { const newImportClause = createImportClause(name, /*namedBindings*/ undefined); const newImportStatement = createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, newImportClause, moduleSpecifier); return { - edits: textChanges.ChangeTracker.with(context, t => t.replaceNode(file, importStatement, newImportStatement)), + edits: textChanges.ChangeTracker.with(toTextChangesContext(context), t => t.replaceNode(file, importStatement, newImportStatement)), renameFilename: undefined, renameLocation: undefined, }; diff --git a/src/services/services.ts b/src/services/services.ts index 4236416fbb3..1692da9872b 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1887,12 +1887,11 @@ namespace ts { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); const span = createTextSpanFromBounds(start, end); - const newLineCharacter = getNewLineOrDefaultFromHost(host); const formatContext = formatting.getFormatContext(formatOptions); return flatMap(deduplicate(errorCodes, equateValues, compareValues), errorCode => { cancellationToken.throwIfCancellationRequested(); - return codefix.getFixes({ errorCode, sourceFile, span, program, newLineCharacter, host, cancellationToken, formatContext }); + return codefix.getFixes({ errorCode, sourceFile, span, program, host, cancellationToken, formatContext }); }); } @@ -1900,10 +1899,9 @@ namespace ts { synchronizeHostData(); Debug.assert(scope.type === "file"); const sourceFile = getValidSourceFile(scope.fileName); - const newLineCharacter = getNewLineOrDefaultFromHost(host); const formatContext = formatting.getFormatContext(formatOptions); - return codefix.getAllFixes({ fixId, sourceFile, program, newLineCharacter, host, cancellationToken, formatContext }); + return codefix.getAllFixes({ fixId, sourceFile, program, host, cancellationToken, formatContext }); } function applyCodeActionCommand(action: CodeActionCommand): Promise; @@ -2134,7 +2132,6 @@ namespace ts { startPosition, endPosition, program: getProgram(), - newLineCharacter: formatOptions ? formatOptions.newLineCharacter : host.getNewLine(), host, formatContext: formatting.getFormatContext(formatOptions), cancellationToken, From f92e6a26c9c52c59865276f9c90d3a97dc4882b6 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 17 Jan 2018 15:14:27 -0800 Subject: [PATCH 129/172] Update issue_template.md --- issue_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/issue_template.md b/issue_template.md index 5e3d612c3c3..d460d66cbac 100644 --- a/issue_template.md +++ b/issue_template.md @@ -1,4 +1,4 @@ - +