From 4dd1e2f8446fd279e26034fc207c317c6a3da986 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 7 Nov 2023 15:59:19 -0800 Subject: [PATCH 1/7] Ensure instantiation expressions have symbols, preventing crash in signature relations (#56064) --- src/compiler/checker.ts | 33 +++++++------ src/compiler/types.ts | 1 + ...sionGenericIntersectionNoCrash1.errors.txt | 23 +++++++++ ...onExpressionGenericIntersectionNoCrash1.js | 23 +++++++++ ...ressionGenericIntersectionNoCrash1.symbols | 32 +++++++++++++ ...xpressionGenericIntersectionNoCrash1.types | 25 ++++++++++ ...sionGenericIntersectionNoCrash2.errors.txt | 28 +++++++++++ ...onExpressionGenericIntersectionNoCrash2.js | 23 +++++++++ ...ressionGenericIntersectionNoCrash2.symbols | 47 +++++++++++++++++++ ...xpressionGenericIntersectionNoCrash2.types | 33 +++++++++++++ tests/baselines/reference/api/typescript.d.ts | 1 + ...onExpressionGenericIntersectionNoCrash1.ts | 12 +++++ ...onExpressionGenericIntersectionNoCrash2.ts | 17 +++++++ 13 files changed, 283 insertions(+), 15 deletions(-) create mode 100644 tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.errors.txt create mode 100644 tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.js create mode 100644 tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.symbols create mode 100644 tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.types create mode 100644 tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.errors.txt create mode 100644 tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.js create mode 100644 tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.symbols create mode 100644 tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.types create mode 100644 tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash1.ts create mode 100644 tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c803b581a7b69..e9bdb4a3d071b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6838,6 +6838,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const typeId = type.id; const symbol = type.symbol; if (symbol) { + const isInstantiationExpressionType = !!(getObjectFlags(type) & ObjectFlags.InstantiationExpressionType); + if (isInstantiationExpressionType) { + const instantiationExpressionType = type as InstantiationExpressionType; + const existing = instantiationExpressionType.node; + if (isTypeQueryNode(existing) && getTypeFromTypeNode(existing) === type) { + const typeNode = serializeExistingTypeNode(context, existing); + if (typeNode) { + return typeNode; + } + } + if (context.visitedTypes?.has(typeId)) { + return createElidedInformationPlaceholder(context); + } + return visitAndTransformType(type, createTypeNodeFromObjectType); + } const isInstanceType = isClassInstanceSide(type) ? SymbolFlags.Type : SymbolFlags.Value; if (isJSConstructor(symbol.valueDeclaration)) { // Instance and static types share the same symbol; only add 'typeof' for the static side. @@ -6869,20 +6884,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } else { - const isInstantiationExpressionType = !!(getObjectFlags(type) & ObjectFlags.InstantiationExpressionType); - if (isInstantiationExpressionType) { - const instantiationExpressionType = type as InstantiationExpressionType; - if (isTypeQueryNode(instantiationExpressionType.node)) { - const typeNode = serializeExistingTypeNode(context, instantiationExpressionType.node); - if (typeNode) { - return typeNode; - } - } - if (context.visitedTypes?.has(typeId)) { - return createElidedInformationPlaceholder(context); - } - return visitAndTransformType(type, createTypeNodeFromObjectType); - } // Anonymous types without a symbol are never circular. return createTypeNodeFromObjectType(type); } @@ -19542,6 +19543,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): AnonymousType { + Debug.assert(type.symbol, "anonymous type must have symbol to be instantiated"); const result = createObjectType(type.objectFlags & ~(ObjectFlags.CouldContainTypeVariablesComputed | ObjectFlags.CouldContainTypeVariables) | ObjectFlags.Instantiated, type.symbol) as AnonymousType; if (type.objectFlags & ObjectFlags.Mapped) { (result as MappedType).declaration = (type as MappedType).declaration; @@ -23074,6 +23076,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // method). Simply do a pairwise comparison of the signatures in the two signature lists instead // of the much more expensive N * M comparison matrix we explore below. We erase type parameters // as they are known to always be the same. + Debug.assertEqual(sourceSignatures.length, targetSignatures.length); for (let i = 0; i < targetSignatures.length; i++) { const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors, intersectionState, incompatibleReporter(sourceSignatures[i], targetSignatures[i])); if (!related) { @@ -35677,7 +35680,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { hasSignatures ||= resolved.callSignatures.length !== 0 || resolved.constructSignatures.length !== 0; hasApplicableSignature ||= callSignatures.length !== 0 || constructSignatures.length !== 0; if (callSignatures !== resolved.callSignatures || constructSignatures !== resolved.constructSignatures) { - const result = createAnonymousType(/*symbol*/ undefined, resolved.members, callSignatures, constructSignatures, resolved.indexInfos) as ResolvedType & InstantiationExpressionType; + const result = createAnonymousType(createSymbol(SymbolFlags.None, InternalSymbolName.InstantiationExpression), resolved.members, callSignatures, constructSignatures, resolved.indexInfos) as ResolvedType & InstantiationExpressionType; result.objectFlags |= ObjectFlags.InstantiationExpressionType; result.node = node; return result; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1926ef4cd7795..34a2835822302 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5955,6 +5955,7 @@ export const enum InternalSymbolName { ExportEquals = "export=", // Export assignment symbol Default = "default", // Default export symbol (technically not wholly internal, but included here for usability) This = "this", + InstantiationExpression = "__instantiationExpression", // Instantiation expressions } /** diff --git a/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.errors.txt b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.errors.txt new file mode 100644 index 0000000000000..4873181c8fb70 --- /dev/null +++ b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.errors.txt @@ -0,0 +1,23 @@ +aliasInstantiationExpressionGenericIntersectionNoCrash1.ts(10,1): error TS2352: Conversion of type '{ new (): ErrImpl; prototype: ErrImpl; } & (() => number)' to type '{ new (): ErrImpl; prototype: ErrImpl; } & (() => string)' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. + Type '{ new (): ErrImpl; prototype: ErrImpl; } & (() => number)' is not comparable to type '{ new (): ErrImpl; prototype: ErrImpl; }'. + Type 'ErrImpl' is not comparable to type 'ErrImpl'. + Type 'number' is not comparable to type 'string'. + + +==== aliasInstantiationExpressionGenericIntersectionNoCrash1.ts (1 errors) ==== + class ErrImpl { + e!: E; + } + + declare const Err: typeof ErrImpl & (() => T); + + type ErrAlias = typeof Err; + + declare const e: ErrAlias; + e as ErrAlias; + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2352: Conversion of type '{ new (): ErrImpl; prototype: ErrImpl; } & (() => number)' to type '{ new (): ErrImpl; prototype: ErrImpl; } & (() => string)' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. +!!! error TS2352: Type '{ new (): ErrImpl; prototype: ErrImpl; } & (() => number)' is not comparable to type '{ new (): ErrImpl; prototype: ErrImpl; }'. +!!! error TS2352: Type 'ErrImpl' is not comparable to type 'ErrImpl'. +!!! error TS2352: Type 'number' is not comparable to type 'string'. + \ No newline at end of file diff --git a/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.js b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.js new file mode 100644 index 0000000000000..33b0409825620 --- /dev/null +++ b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.js @@ -0,0 +1,23 @@ +//// [tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash1.ts] //// + +//// [aliasInstantiationExpressionGenericIntersectionNoCrash1.ts] +class ErrImpl { + e!: E; +} + +declare const Err: typeof ErrImpl & (() => T); + +type ErrAlias = typeof Err; + +declare const e: ErrAlias; +e as ErrAlias; + + +//// [aliasInstantiationExpressionGenericIntersectionNoCrash1.js] +"use strict"; +var ErrImpl = /** @class */ (function () { + function ErrImpl() { + } + return ErrImpl; +}()); +e; diff --git a/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.symbols b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.symbols new file mode 100644 index 0000000000000..7cdb48a8d5e51 --- /dev/null +++ b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.symbols @@ -0,0 +1,32 @@ +//// [tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash1.ts] //// + +=== aliasInstantiationExpressionGenericIntersectionNoCrash1.ts === +class ErrImpl { +>ErrImpl : Symbol(ErrImpl, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 0, 0)) +>E : Symbol(E, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 0, 14)) + + e!: E; +>e : Symbol(ErrImpl.e, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 0, 18)) +>E : Symbol(E, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 0, 14)) +} + +declare const Err: typeof ErrImpl & (() => T); +>Err : Symbol(Err, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 4, 13)) +>ErrImpl : Symbol(ErrImpl, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 0, 0)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 4, 38)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 4, 38)) + +type ErrAlias = typeof Err; +>ErrAlias : Symbol(ErrAlias, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 4, 49)) +>U : Symbol(U, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 6, 14)) +>Err : Symbol(Err, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 4, 13)) +>U : Symbol(U, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 6, 14)) + +declare const e: ErrAlias; +>e : Symbol(e, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 8, 13)) +>ErrAlias : Symbol(ErrAlias, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 4, 49)) + +e as ErrAlias; +>e : Symbol(e, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 8, 13)) +>ErrAlias : Symbol(ErrAlias, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 4, 49)) + diff --git a/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.types b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.types new file mode 100644 index 0000000000000..287ec645bc8c5 --- /dev/null +++ b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.types @@ -0,0 +1,25 @@ +//// [tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash1.ts] //// + +=== aliasInstantiationExpressionGenericIntersectionNoCrash1.ts === +class ErrImpl { +>ErrImpl : ErrImpl + + e!: E; +>e : E +} + +declare const Err: typeof ErrImpl & (() => T); +>Err : typeof ErrImpl & (() => T) +>ErrImpl : typeof ErrImpl + +type ErrAlias = typeof Err; +>ErrAlias : { new (): ErrImpl; prototype: ErrImpl; } & (() => U) +>Err : typeof ErrImpl & (() => T) + +declare const e: ErrAlias; +>e : { new (): ErrImpl; prototype: ErrImpl; } & (() => number) + +e as ErrAlias; +>e as ErrAlias : { new (): ErrImpl; prototype: ErrImpl; } & (() => string) +>e : { new (): ErrImpl; prototype: ErrImpl; } & (() => number) + diff --git a/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.errors.txt b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.errors.txt new file mode 100644 index 0000000000000..54c1fc637f6e2 --- /dev/null +++ b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.errors.txt @@ -0,0 +1,28 @@ +aliasInstantiationExpressionGenericIntersectionNoCrash2.ts(15,1): error TS2352: Conversion of type 'Wat' to type 'Wat' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. + Type 'Wat' is not comparable to type '{ new (): Class; prototype: Class; }'. + Type 'Class' is not comparable to type 'Class'. + Type 'number' is not comparable to type 'string'. + + +==== aliasInstantiationExpressionGenericIntersectionNoCrash2.ts (1 errors) ==== + declare class Class { + x: T; + } + + declare function fn(): T; + + + type ClassAlias = typeof Class; + type FnAlias = typeof fn; + + type Wat = ClassAlias & FnAlias; + + + declare const wat: Wat; + wat as Wat; + ~~~~~~~~~~~~~~~~~~ +!!! error TS2352: Conversion of type 'Wat' to type 'Wat' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. +!!! error TS2352: Type 'Wat' is not comparable to type '{ new (): Class; prototype: Class; }'. +!!! error TS2352: Type 'Class' is not comparable to type 'Class'. +!!! error TS2352: Type 'number' is not comparable to type 'string'. + \ No newline at end of file diff --git a/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.js b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.js new file mode 100644 index 0000000000000..0755056e2d457 --- /dev/null +++ b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.js @@ -0,0 +1,23 @@ +//// [tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash2.ts] //// + +//// [aliasInstantiationExpressionGenericIntersectionNoCrash2.ts] +declare class Class { + x: T; +} + +declare function fn(): T; + + +type ClassAlias = typeof Class; +type FnAlias = typeof fn; + +type Wat = ClassAlias & FnAlias; + + +declare const wat: Wat; +wat as Wat; + + +//// [aliasInstantiationExpressionGenericIntersectionNoCrash2.js] +"use strict"; +wat; diff --git a/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.symbols b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.symbols new file mode 100644 index 0000000000000..f73a051b0c705 --- /dev/null +++ b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.symbols @@ -0,0 +1,47 @@ +//// [tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash2.ts] //// + +=== aliasInstantiationExpressionGenericIntersectionNoCrash2.ts === +declare class Class { +>Class : Symbol(Class, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 0, 0)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 0, 20)) + + x: T; +>x : Symbol(Class.x, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 0, 24)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 0, 20)) +} + +declare function fn(): T; +>fn : Symbol(fn, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 2, 1)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 4, 20)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 4, 20)) + + +type ClassAlias = typeof Class; +>ClassAlias : Symbol(ClassAlias, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 4, 28)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 7, 16)) +>Class : Symbol(Class, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 0, 0)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 7, 16)) + +type FnAlias = typeof fn; +>FnAlias : Symbol(FnAlias, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 7, 37)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 8, 13)) +>fn : Symbol(fn, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 2, 1)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 8, 13)) + +type Wat = ClassAlias & FnAlias; +>Wat : Symbol(Wat, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 8, 31)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 10, 9)) +>ClassAlias : Symbol(ClassAlias, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 4, 28)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 10, 9)) +>FnAlias : Symbol(FnAlias, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 7, 37)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 10, 9)) + + +declare const wat: Wat; +>wat : Symbol(wat, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 13, 13)) +>Wat : Symbol(Wat, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 8, 31)) + +wat as Wat; +>wat : Symbol(wat, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 13, 13)) +>Wat : Symbol(Wat, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 8, 31)) + diff --git a/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.types b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.types new file mode 100644 index 0000000000000..8ddaf31d491f2 --- /dev/null +++ b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.types @@ -0,0 +1,33 @@ +//// [tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash2.ts] //// + +=== aliasInstantiationExpressionGenericIntersectionNoCrash2.ts === +declare class Class { +>Class : Class + + x: T; +>x : T +} + +declare function fn(): T; +>fn : () => T + + +type ClassAlias = typeof Class; +>ClassAlias : typeof Class +>Class : typeof Class + +type FnAlias = typeof fn; +>FnAlias : typeof fn +>fn : () => T_1 + +type Wat = ClassAlias & FnAlias; +>Wat : Wat + + +declare const wat: Wat; +>wat : Wat + +wat as Wat; +>wat as Wat : Wat +>wat : Wat + diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 3f7f78fd00828..eddf759a20b84 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -7049,6 +7049,7 @@ declare namespace ts { ExportEquals = "export=", Default = "default", This = "this", + InstantiationExpression = "__instantiationExpression", } /** * This represents a string whose leading underscore have been escaped by adding extra leading underscores. diff --git a/tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash1.ts b/tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash1.ts new file mode 100644 index 0000000000000..d43bc399df8ee --- /dev/null +++ b/tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash1.ts @@ -0,0 +1,12 @@ +// @strict: true + +class ErrImpl { + e!: E; +} + +declare const Err: typeof ErrImpl & (() => T); + +type ErrAlias = typeof Err; + +declare const e: ErrAlias; +e as ErrAlias; diff --git a/tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash2.ts b/tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash2.ts new file mode 100644 index 0000000000000..b81740c9513c6 --- /dev/null +++ b/tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash2.ts @@ -0,0 +1,17 @@ +// @strict: true + +declare class Class { + x: T; +} + +declare function fn(): T; + + +type ClassAlias = typeof Class; +type FnAlias = typeof fn; + +type Wat = ClassAlias & FnAlias; + + +declare const wat: Wat; +wat as Wat; From 50f48840a32e3b8e2498967a09e7119203f5131b Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Wed, 8 Nov 2023 06:20:08 +0000 Subject: [PATCH 2/7] Update package-lock.json --- package-lock.json | 120 +++++++++++++++++++++++----------------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1090f8f0ac143..993d7ca04ca3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -893,9 +893,9 @@ } }, "node_modules/@types/chai": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.9.tgz", - "integrity": "sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg==", + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.10.tgz", + "integrity": "sha512-of+ICnbqjmFCiixUnqRulbylyXQrPqIGf/B3Jax1wIF3DvSheysQxAWvqHhZiW3IQrycvokcLcFQlveGp+vyNg==", "dev": true }, "node_modules/@types/glob": { @@ -909,21 +909,21 @@ } }, "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-zONci81DZYCZjiLe0r6equvZut0b+dBRPBN5kBDjsONnutYNtJMoWQ9uR2RkL1gLG9NMTzvf+29e5RFfPbeKhQ==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", - "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/microsoft__typescript-etw": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@types/microsoft__typescript-etw/-/microsoft__typescript-etw-0.1.2.tgz", - "integrity": "sha512-tOI1MLj8zY7R5LWdnY/mi+6A1hxLOO0zwrLBiDHytTM2NEoZGmQFgM8rsnu7SXchKX1Gvz2ak8Y77nVqJ5uAIw==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@types/microsoft__typescript-etw/-/microsoft__typescript-etw-0.1.3.tgz", + "integrity": "sha512-qbO0IoTPJERhGWOvdw9iQbRjM7OGc6+fHVziSKcw566BDSQpEdkGxXcE8BKSHJOP6mkTRobf4QxqRE/aKhrCxg==", "dev": true }, "node_modules/@types/minimatch": { @@ -933,42 +933,42 @@ "dev": true }, "node_modules/@types/minimist": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.4.tgz", - "integrity": "sha512-Kfe/D3hxHTusnPNRbycJE1N77WHDsdS4AjUYIzlDzhDrS47NrwuL3YW4VITxwR7KCVpzwgy4Rbj829KSSQmwXQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", "dev": true }, "node_modules/@types/mocha": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.3.tgz", - "integrity": "sha512-RsOPImTriV/OE4A9qKjMtk2MnXiuLLbcO3nCXK+kvq4nr0iMfFgpjaX3MPLb6f7+EL1FGSelYvuJMV6REH+ZPQ==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.4.tgz", + "integrity": "sha512-xKU7bUjiFTIttpWaIZ9qvgg+22O1nmbA+HRxdlR+u6TWsGfmFdXrheJoK4fFxrHNVIOBDvDNKZG+LYBpMHpX3w==", "dev": true }, "node_modules/@types/ms": { - "version": "0.7.33", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.33.tgz", - "integrity": "sha512-AuHIyzR5Hea7ij0P9q7vx7xu4z0C28ucwjAZC0ja7JhINyCnOw8/DnvAPQQ9TfOlCtZAmCERKQX9+o1mgQhuOQ==", + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", "dev": true }, "node_modules/@types/node": { - "version": "20.8.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", - "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", + "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", + "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==", "dev": true }, "node_modules/@types/source-map-support": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.5.9.tgz", - "integrity": "sha512-91Jf4LyPAObBTFbpW3bSDK1ncdwXohvlBmzffSj7/44SY+1mD/HhesdfspCMxPIJwllgN2G4eVFatGs4Zw/lnw==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-tgVP2H469x9zq34Z0m/fgPewGhg/MLClalNOiPIzQlXrSS2YrKu/xCdSCKnEDwkFha51VKEKB6A9wW26/ZNwzA==", "dev": true, "dependencies": { "source-map": "^0.6.0" @@ -4507,9 +4507,9 @@ } }, "@types/chai": { - "version": "4.3.9", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.9.tgz", - "integrity": "sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg==", + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.10.tgz", + "integrity": "sha512-of+ICnbqjmFCiixUnqRulbylyXQrPqIGf/B3Jax1wIF3DvSheysQxAWvqHhZiW3IQrycvokcLcFQlveGp+vyNg==", "dev": true }, "@types/glob": { @@ -4523,21 +4523,21 @@ } }, "@types/istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-zONci81DZYCZjiLe0r6equvZut0b+dBRPBN5kBDjsONnutYNtJMoWQ9uR2RkL1gLG9NMTzvf+29e5RFfPbeKhQ==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true }, "@types/json-schema": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", - "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "@types/microsoft__typescript-etw": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@types/microsoft__typescript-etw/-/microsoft__typescript-etw-0.1.2.tgz", - "integrity": "sha512-tOI1MLj8zY7R5LWdnY/mi+6A1hxLOO0zwrLBiDHytTM2NEoZGmQFgM8rsnu7SXchKX1Gvz2ak8Y77nVqJ5uAIw==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@types/microsoft__typescript-etw/-/microsoft__typescript-etw-0.1.3.tgz", + "integrity": "sha512-qbO0IoTPJERhGWOvdw9iQbRjM7OGc6+fHVziSKcw566BDSQpEdkGxXcE8BKSHJOP6mkTRobf4QxqRE/aKhrCxg==", "dev": true }, "@types/minimatch": { @@ -4547,42 +4547,42 @@ "dev": true }, "@types/minimist": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.4.tgz", - "integrity": "sha512-Kfe/D3hxHTusnPNRbycJE1N77WHDsdS4AjUYIzlDzhDrS47NrwuL3YW4VITxwR7KCVpzwgy4Rbj829KSSQmwXQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", "dev": true }, "@types/mocha": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.3.tgz", - "integrity": "sha512-RsOPImTriV/OE4A9qKjMtk2MnXiuLLbcO3nCXK+kvq4nr0iMfFgpjaX3MPLb6f7+EL1FGSelYvuJMV6REH+ZPQ==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.4.tgz", + "integrity": "sha512-xKU7bUjiFTIttpWaIZ9qvgg+22O1nmbA+HRxdlR+u6TWsGfmFdXrheJoK4fFxrHNVIOBDvDNKZG+LYBpMHpX3w==", "dev": true }, "@types/ms": { - "version": "0.7.33", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.33.tgz", - "integrity": "sha512-AuHIyzR5Hea7ij0P9q7vx7xu4z0C28ucwjAZC0ja7JhINyCnOw8/DnvAPQQ9TfOlCtZAmCERKQX9+o1mgQhuOQ==", + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", "dev": true }, "@types/node": { - "version": "20.8.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", - "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", + "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", "dev": true, "requires": { "undici-types": "~5.26.4" } }, "@types/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", + "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==", "dev": true }, "@types/source-map-support": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.5.9.tgz", - "integrity": "sha512-91Jf4LyPAObBTFbpW3bSDK1ncdwXohvlBmzffSj7/44SY+1mD/HhesdfspCMxPIJwllgN2G4eVFatGs4Zw/lnw==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-tgVP2H469x9zq34Z0m/fgPewGhg/MLClalNOiPIzQlXrSS2YrKu/xCdSCKnEDwkFha51VKEKB6A9wW26/ZNwzA==", "dev": true, "requires": { "source-map": "^0.6.0" From 7b96c28e8f755ef8ba3dff00714f3f02e4d5a268 Mon Sep 17 00:00:00 2001 From: Zzzen Date: Thu, 9 Nov 2023 07:06:40 +0800 Subject: [PATCH 3/7] Add Support for Using Aliased Discriminants in Conditional Statements (#56173) --- src/compiler/checker.ts | 4 + ...controlFlowAliasedDiscriminants.errors.txt | 129 ++++++ .../controlFlowAliasedDiscriminants.js | 182 +++++++++ .../controlFlowAliasedDiscriminants.symbols | 327 +++++++++++++++ .../controlFlowAliasedDiscriminants.types | 378 ++++++++++++++++++ .../controlFlowAliasedDiscriminants.ts | 106 +++++ 6 files changed, 1126 insertions(+) create mode 100644 tests/baselines/reference/controlFlowAliasedDiscriminants.errors.txt create mode 100644 tests/baselines/reference/controlFlowAliasedDiscriminants.js create mode 100644 tests/baselines/reference/controlFlowAliasedDiscriminants.symbols create mode 100644 tests/baselines/reference/controlFlowAliasedDiscriminants.types create mode 100644 tests/cases/compiler/controlFlowAliasedDiscriminants.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e9bdb4a3d071b..4a294a2474f26 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27244,6 +27244,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { case SyntaxKind.ElementAccessExpression: // The resolvedSymbol property is initialized by checkPropertyAccess or checkElementAccess before we get here. return isConstantReference((node as AccessExpression).expression) && isReadonlySymbol(getNodeLinks(node).resolvedSymbol || unknownSymbol); + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + const rootDeclaration = getRootDeclaration(node.parent); + return isVariableDeclaration(rootDeclaration) && isVarConstLike(rootDeclaration); } return false; } diff --git a/tests/baselines/reference/controlFlowAliasedDiscriminants.errors.txt b/tests/baselines/reference/controlFlowAliasedDiscriminants.errors.txt new file mode 100644 index 0000000000000..81779d897385d --- /dev/null +++ b/tests/baselines/reference/controlFlowAliasedDiscriminants.errors.txt @@ -0,0 +1,129 @@ +controlFlowAliasedDiscriminants.ts(39,9): error TS18048: 'data1' is possibly 'undefined'. +controlFlowAliasedDiscriminants.ts(40,9): error TS18048: 'data2' is possibly 'undefined'. +controlFlowAliasedDiscriminants.ts(65,9): error TS18048: 'bar2' is possibly 'undefined'. +controlFlowAliasedDiscriminants.ts(66,9): error TS18048: 'bar3' is possibly 'undefined'. +controlFlowAliasedDiscriminants.ts(86,14): error TS1360: Type 'string | number' does not satisfy the expected type 'string'. + Type 'number' is not assignable to type 'string'. +controlFlowAliasedDiscriminants.ts(98,19): error TS1360: Type 'string | number' does not satisfy the expected type 'string'. + Type 'number' is not assignable to type 'string'. + + +==== controlFlowAliasedDiscriminants.ts (6 errors) ==== + type UseQueryResult = { + isSuccess: false; + data: undefined; + } | { + isSuccess: true; + data: T + }; + + function useQuery(): UseQueryResult { + return { + isSuccess: false, + data: undefined, + }; + } + + const { data: data1, isSuccess: isSuccess1 } = useQuery(); + const { data: data2, isSuccess: isSuccess2 } = useQuery(); + const { data: data3, isSuccess: isSuccess3 } = useQuery(); + + if (isSuccess1 && isSuccess2 && isSuccess3) { + data1.toExponential(); // should ok + data2.toExponential(); // should ok + data3.toExponential(); // should ok + } + + const areSuccess = isSuccess1 && isSuccess2 && isSuccess3; + if (areSuccess) { + data1.toExponential(); // should ok + data2.toExponential(); // should ok + data3.toExponential(); // should ok + } + + { + let { data: data1, isSuccess: isSuccess1 } = useQuery(); + let { data: data2, isSuccess: isSuccess2 } = useQuery(); + const { data: data3, isSuccess: isSuccess3 } = useQuery(); + const areSuccess = isSuccess1 && isSuccess2 && isSuccess3; + if (areSuccess) { + data1.toExponential(); // should error + ~~~~~ +!!! error TS18048: 'data1' is possibly 'undefined'. + data2.toExponential(); // should error + ~~~~~ +!!! error TS18048: 'data2' is possibly 'undefined'. + data3.toExponential(); // should ok + } + } + + declare function getArrayResult(): [true, number] | [false, undefined]; + { + const [foo1, bar1] = getArrayResult(); + const [foo2, bar2] = getArrayResult(); + const [foo3, bar3] = getArrayResult(); + const arrayAllSuccess = foo1 && foo2 && foo3; + if (arrayAllSuccess) { + bar1.toExponential(); // should ok + bar2.toExponential(); // should ok + bar3.toExponential(); // should ok + } + } + + { + const [foo1, bar1] = getArrayResult(); + let [foo2, bar2] = getArrayResult(); + let [foo3, bar3] = getArrayResult(); + const arrayAllSuccess = foo1 && foo2 && foo3; + if (arrayAllSuccess) { + bar1.toExponential(); // should ok + bar2.toExponential(); // should error + ~~~~ +!!! error TS18048: 'bar2' is possibly 'undefined'. + bar3.toExponential(); // should error + ~~~~ +!!! error TS18048: 'bar3' is possibly 'undefined'. + } + } + + type Nested = { + type: 'string'; + resp: { + data: string + } + } | { + type: 'number'; + resp: { + data: number; + } + } + + { + let resp!: Nested; + const { resp: { data }, type } = resp; + if (type === 'string') { + data satisfies string; + ~~~~~~~~~ +!!! error TS1360: Type 'string | number' does not satisfy the expected type 'string'. +!!! error TS1360: Type 'number' is not assignable to type 'string'. + } + if (resp.type === 'string') { + resp.resp.data satisfies string; + } + } + + { + + let resp!: Nested; + const { resp: { data: dataAlias }, type } = resp; + if (type === 'string') { + dataAlias satisfies string; + ~~~~~~~~~ +!!! error TS1360: Type 'string | number' does not satisfy the expected type 'string'. +!!! error TS1360: Type 'number' is not assignable to type 'string'. + } + if (resp.type === 'string') { + resp.resp.data satisfies string; + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/controlFlowAliasedDiscriminants.js b/tests/baselines/reference/controlFlowAliasedDiscriminants.js new file mode 100644 index 0000000000000..0be7340f29f63 --- /dev/null +++ b/tests/baselines/reference/controlFlowAliasedDiscriminants.js @@ -0,0 +1,182 @@ +//// [tests/cases/compiler/controlFlowAliasedDiscriminants.ts] //// + +//// [controlFlowAliasedDiscriminants.ts] +type UseQueryResult = { + isSuccess: false; + data: undefined; +} | { + isSuccess: true; + data: T +}; + +function useQuery(): UseQueryResult { + return { + isSuccess: false, + data: undefined, + }; +} + +const { data: data1, isSuccess: isSuccess1 } = useQuery(); +const { data: data2, isSuccess: isSuccess2 } = useQuery(); +const { data: data3, isSuccess: isSuccess3 } = useQuery(); + +if (isSuccess1 && isSuccess2 && isSuccess3) { + data1.toExponential(); // should ok + data2.toExponential(); // should ok + data3.toExponential(); // should ok +} + +const areSuccess = isSuccess1 && isSuccess2 && isSuccess3; +if (areSuccess) { + data1.toExponential(); // should ok + data2.toExponential(); // should ok + data3.toExponential(); // should ok +} + +{ + let { data: data1, isSuccess: isSuccess1 } = useQuery(); + let { data: data2, isSuccess: isSuccess2 } = useQuery(); + const { data: data3, isSuccess: isSuccess3 } = useQuery(); + const areSuccess = isSuccess1 && isSuccess2 && isSuccess3; + if (areSuccess) { + data1.toExponential(); // should error + data2.toExponential(); // should error + data3.toExponential(); // should ok + } +} + +declare function getArrayResult(): [true, number] | [false, undefined]; +{ + const [foo1, bar1] = getArrayResult(); + const [foo2, bar2] = getArrayResult(); + const [foo3, bar3] = getArrayResult(); + const arrayAllSuccess = foo1 && foo2 && foo3; + if (arrayAllSuccess) { + bar1.toExponential(); // should ok + bar2.toExponential(); // should ok + bar3.toExponential(); // should ok + } +} + +{ + const [foo1, bar1] = getArrayResult(); + let [foo2, bar2] = getArrayResult(); + let [foo3, bar3] = getArrayResult(); + const arrayAllSuccess = foo1 && foo2 && foo3; + if (arrayAllSuccess) { + bar1.toExponential(); // should ok + bar2.toExponential(); // should error + bar3.toExponential(); // should error + } +} + +type Nested = { + type: 'string'; + resp: { + data: string + } +} | { + type: 'number'; + resp: { + data: number; + } +} + +{ + let resp!: Nested; + const { resp: { data }, type } = resp; + if (type === 'string') { + data satisfies string; + } + if (resp.type === 'string') { + resp.resp.data satisfies string; + } +} + +{ + + let resp!: Nested; + const { resp: { data: dataAlias }, type } = resp; + if (type === 'string') { + dataAlias satisfies string; + } + if (resp.type === 'string') { + resp.resp.data satisfies string; + } +} + + +//// [controlFlowAliasedDiscriminants.js] +function useQuery() { + return { + isSuccess: false, + data: undefined, + }; +} +var _a = useQuery(), data1 = _a.data, isSuccess1 = _a.isSuccess; +var _b = useQuery(), data2 = _b.data, isSuccess2 = _b.isSuccess; +var _c = useQuery(), data3 = _c.data, isSuccess3 = _c.isSuccess; +if (isSuccess1 && isSuccess2 && isSuccess3) { + data1.toExponential(); // should ok + data2.toExponential(); // should ok + data3.toExponential(); // should ok +} +var areSuccess = isSuccess1 && isSuccess2 && isSuccess3; +if (areSuccess) { + data1.toExponential(); // should ok + data2.toExponential(); // should ok + data3.toExponential(); // should ok +} +{ + var _d = useQuery(), data1_1 = _d.data, isSuccess1_1 = _d.isSuccess; + var _e = useQuery(), data2_1 = _e.data, isSuccess2_1 = _e.isSuccess; + var _f = useQuery(), data3_1 = _f.data, isSuccess3_1 = _f.isSuccess; + var areSuccess_1 = isSuccess1_1 && isSuccess2_1 && isSuccess3_1; + if (areSuccess_1) { + data1_1.toExponential(); // should error + data2_1.toExponential(); // should error + data3_1.toExponential(); // should ok + } +} +{ + var _g = getArrayResult(), foo1 = _g[0], bar1 = _g[1]; + var _h = getArrayResult(), foo2 = _h[0], bar2 = _h[1]; + var _j = getArrayResult(), foo3 = _j[0], bar3 = _j[1]; + var arrayAllSuccess = foo1 && foo2 && foo3; + if (arrayAllSuccess) { + bar1.toExponential(); // should ok + bar2.toExponential(); // should ok + bar3.toExponential(); // should ok + } +} +{ + var _k = getArrayResult(), foo1 = _k[0], bar1 = _k[1]; + var _l = getArrayResult(), foo2 = _l[0], bar2 = _l[1]; + var _m = getArrayResult(), foo3 = _m[0], bar3 = _m[1]; + var arrayAllSuccess = foo1 && foo2 && foo3; + if (arrayAllSuccess) { + bar1.toExponential(); // should ok + bar2.toExponential(); // should error + bar3.toExponential(); // should error + } +} +{ + var resp = void 0; + var data = resp.resp.data, type = resp.type; + if (type === 'string') { + data; + } + if (resp.type === 'string') { + resp.resp.data; + } +} +{ + var resp = void 0; + var dataAlias = resp.resp.data, type = resp.type; + if (type === 'string') { + dataAlias; + } + if (resp.type === 'string') { + resp.resp.data; + } +} diff --git a/tests/baselines/reference/controlFlowAliasedDiscriminants.symbols b/tests/baselines/reference/controlFlowAliasedDiscriminants.symbols new file mode 100644 index 0000000000000..971b4be1c5c73 --- /dev/null +++ b/tests/baselines/reference/controlFlowAliasedDiscriminants.symbols @@ -0,0 +1,327 @@ +//// [tests/cases/compiler/controlFlowAliasedDiscriminants.ts] //// + +=== controlFlowAliasedDiscriminants.ts === +type UseQueryResult = { +>UseQueryResult : Symbol(UseQueryResult, Decl(controlFlowAliasedDiscriminants.ts, 0, 0)) +>T : Symbol(T, Decl(controlFlowAliasedDiscriminants.ts, 0, 20)) + + isSuccess: false; +>isSuccess : Symbol(isSuccess, Decl(controlFlowAliasedDiscriminants.ts, 0, 26)) + + data: undefined; +>data : Symbol(data, Decl(controlFlowAliasedDiscriminants.ts, 1, 21)) + +} | { + isSuccess: true; +>isSuccess : Symbol(isSuccess, Decl(controlFlowAliasedDiscriminants.ts, 3, 5)) + + data: T +>data : Symbol(data, Decl(controlFlowAliasedDiscriminants.ts, 4, 20)) +>T : Symbol(T, Decl(controlFlowAliasedDiscriminants.ts, 0, 20)) + +}; + +function useQuery(): UseQueryResult { +>useQuery : Symbol(useQuery, Decl(controlFlowAliasedDiscriminants.ts, 6, 2)) +>UseQueryResult : Symbol(UseQueryResult, Decl(controlFlowAliasedDiscriminants.ts, 0, 0)) + + return { + isSuccess: false, +>isSuccess : Symbol(isSuccess, Decl(controlFlowAliasedDiscriminants.ts, 9, 12)) + + data: undefined, +>data : Symbol(data, Decl(controlFlowAliasedDiscriminants.ts, 10, 25)) +>undefined : Symbol(undefined) + + }; +} + +const { data: data1, isSuccess: isSuccess1 } = useQuery(); +>data : Symbol(data, Decl(controlFlowAliasedDiscriminants.ts, 1, 21), Decl(controlFlowAliasedDiscriminants.ts, 4, 20)) +>data1 : Symbol(data1, Decl(controlFlowAliasedDiscriminants.ts, 15, 7)) +>isSuccess : Symbol(isSuccess, Decl(controlFlowAliasedDiscriminants.ts, 0, 26), Decl(controlFlowAliasedDiscriminants.ts, 3, 5)) +>isSuccess1 : Symbol(isSuccess1, Decl(controlFlowAliasedDiscriminants.ts, 15, 20)) +>useQuery : Symbol(useQuery, Decl(controlFlowAliasedDiscriminants.ts, 6, 2)) + +const { data: data2, isSuccess: isSuccess2 } = useQuery(); +>data : Symbol(data, Decl(controlFlowAliasedDiscriminants.ts, 1, 21), Decl(controlFlowAliasedDiscriminants.ts, 4, 20)) +>data2 : Symbol(data2, Decl(controlFlowAliasedDiscriminants.ts, 16, 7)) +>isSuccess : Symbol(isSuccess, Decl(controlFlowAliasedDiscriminants.ts, 0, 26), Decl(controlFlowAliasedDiscriminants.ts, 3, 5)) +>isSuccess2 : Symbol(isSuccess2, Decl(controlFlowAliasedDiscriminants.ts, 16, 20)) +>useQuery : Symbol(useQuery, Decl(controlFlowAliasedDiscriminants.ts, 6, 2)) + +const { data: data3, isSuccess: isSuccess3 } = useQuery(); +>data : Symbol(data, Decl(controlFlowAliasedDiscriminants.ts, 1, 21), Decl(controlFlowAliasedDiscriminants.ts, 4, 20)) +>data3 : Symbol(data3, Decl(controlFlowAliasedDiscriminants.ts, 17, 7)) +>isSuccess : Symbol(isSuccess, Decl(controlFlowAliasedDiscriminants.ts, 0, 26), Decl(controlFlowAliasedDiscriminants.ts, 3, 5)) +>isSuccess3 : Symbol(isSuccess3, Decl(controlFlowAliasedDiscriminants.ts, 17, 20)) +>useQuery : Symbol(useQuery, Decl(controlFlowAliasedDiscriminants.ts, 6, 2)) + +if (isSuccess1 && isSuccess2 && isSuccess3) { +>isSuccess1 : Symbol(isSuccess1, Decl(controlFlowAliasedDiscriminants.ts, 15, 20)) +>isSuccess2 : Symbol(isSuccess2, Decl(controlFlowAliasedDiscriminants.ts, 16, 20)) +>isSuccess3 : Symbol(isSuccess3, Decl(controlFlowAliasedDiscriminants.ts, 17, 20)) + + data1.toExponential(); // should ok +>data1.toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) +>data1 : Symbol(data1, Decl(controlFlowAliasedDiscriminants.ts, 15, 7)) +>toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) + + data2.toExponential(); // should ok +>data2.toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) +>data2 : Symbol(data2, Decl(controlFlowAliasedDiscriminants.ts, 16, 7)) +>toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) + + data3.toExponential(); // should ok +>data3.toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) +>data3 : Symbol(data3, Decl(controlFlowAliasedDiscriminants.ts, 17, 7)) +>toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) +} + +const areSuccess = isSuccess1 && isSuccess2 && isSuccess3; +>areSuccess : Symbol(areSuccess, Decl(controlFlowAliasedDiscriminants.ts, 25, 5)) +>isSuccess1 : Symbol(isSuccess1, Decl(controlFlowAliasedDiscriminants.ts, 15, 20)) +>isSuccess2 : Symbol(isSuccess2, Decl(controlFlowAliasedDiscriminants.ts, 16, 20)) +>isSuccess3 : Symbol(isSuccess3, Decl(controlFlowAliasedDiscriminants.ts, 17, 20)) + +if (areSuccess) { +>areSuccess : Symbol(areSuccess, Decl(controlFlowAliasedDiscriminants.ts, 25, 5)) + + data1.toExponential(); // should ok +>data1.toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) +>data1 : Symbol(data1, Decl(controlFlowAliasedDiscriminants.ts, 15, 7)) +>toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) + + data2.toExponential(); // should ok +>data2.toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) +>data2 : Symbol(data2, Decl(controlFlowAliasedDiscriminants.ts, 16, 7)) +>toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) + + data3.toExponential(); // should ok +>data3.toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) +>data3 : Symbol(data3, Decl(controlFlowAliasedDiscriminants.ts, 17, 7)) +>toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) +} + +{ + let { data: data1, isSuccess: isSuccess1 } = useQuery(); +>data : Symbol(data, Decl(controlFlowAliasedDiscriminants.ts, 1, 21), Decl(controlFlowAliasedDiscriminants.ts, 4, 20)) +>data1 : Symbol(data1, Decl(controlFlowAliasedDiscriminants.ts, 33, 9)) +>isSuccess : Symbol(isSuccess, Decl(controlFlowAliasedDiscriminants.ts, 0, 26), Decl(controlFlowAliasedDiscriminants.ts, 3, 5)) +>isSuccess1 : Symbol(isSuccess1, Decl(controlFlowAliasedDiscriminants.ts, 33, 22)) +>useQuery : Symbol(useQuery, Decl(controlFlowAliasedDiscriminants.ts, 6, 2)) + + let { data: data2, isSuccess: isSuccess2 } = useQuery(); +>data : Symbol(data, Decl(controlFlowAliasedDiscriminants.ts, 1, 21), Decl(controlFlowAliasedDiscriminants.ts, 4, 20)) +>data2 : Symbol(data2, Decl(controlFlowAliasedDiscriminants.ts, 34, 9)) +>isSuccess : Symbol(isSuccess, Decl(controlFlowAliasedDiscriminants.ts, 0, 26), Decl(controlFlowAliasedDiscriminants.ts, 3, 5)) +>isSuccess2 : Symbol(isSuccess2, Decl(controlFlowAliasedDiscriminants.ts, 34, 22)) +>useQuery : Symbol(useQuery, Decl(controlFlowAliasedDiscriminants.ts, 6, 2)) + + const { data: data3, isSuccess: isSuccess3 } = useQuery(); +>data : Symbol(data, Decl(controlFlowAliasedDiscriminants.ts, 1, 21), Decl(controlFlowAliasedDiscriminants.ts, 4, 20)) +>data3 : Symbol(data3, Decl(controlFlowAliasedDiscriminants.ts, 35, 11)) +>isSuccess : Symbol(isSuccess, Decl(controlFlowAliasedDiscriminants.ts, 0, 26), Decl(controlFlowAliasedDiscriminants.ts, 3, 5)) +>isSuccess3 : Symbol(isSuccess3, Decl(controlFlowAliasedDiscriminants.ts, 35, 24)) +>useQuery : Symbol(useQuery, Decl(controlFlowAliasedDiscriminants.ts, 6, 2)) + + const areSuccess = isSuccess1 && isSuccess2 && isSuccess3; +>areSuccess : Symbol(areSuccess, Decl(controlFlowAliasedDiscriminants.ts, 36, 9)) +>isSuccess1 : Symbol(isSuccess1, Decl(controlFlowAliasedDiscriminants.ts, 33, 22)) +>isSuccess2 : Symbol(isSuccess2, Decl(controlFlowAliasedDiscriminants.ts, 34, 22)) +>isSuccess3 : Symbol(isSuccess3, Decl(controlFlowAliasedDiscriminants.ts, 35, 24)) + + if (areSuccess) { +>areSuccess : Symbol(areSuccess, Decl(controlFlowAliasedDiscriminants.ts, 36, 9)) + + data1.toExponential(); // should error +>data1.toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) +>data1 : Symbol(data1, Decl(controlFlowAliasedDiscriminants.ts, 33, 9)) +>toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) + + data2.toExponential(); // should error +>data2.toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) +>data2 : Symbol(data2, Decl(controlFlowAliasedDiscriminants.ts, 34, 9)) +>toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) + + data3.toExponential(); // should ok +>data3.toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) +>data3 : Symbol(data3, Decl(controlFlowAliasedDiscriminants.ts, 35, 11)) +>toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) + } +} + +declare function getArrayResult(): [true, number] | [false, undefined]; +>getArrayResult : Symbol(getArrayResult, Decl(controlFlowAliasedDiscriminants.ts, 42, 1)) +{ + const [foo1, bar1] = getArrayResult(); +>foo1 : Symbol(foo1, Decl(controlFlowAliasedDiscriminants.ts, 46, 11)) +>bar1 : Symbol(bar1, Decl(controlFlowAliasedDiscriminants.ts, 46, 16)) +>getArrayResult : Symbol(getArrayResult, Decl(controlFlowAliasedDiscriminants.ts, 42, 1)) + + const [foo2, bar2] = getArrayResult(); +>foo2 : Symbol(foo2, Decl(controlFlowAliasedDiscriminants.ts, 47, 11)) +>bar2 : Symbol(bar2, Decl(controlFlowAliasedDiscriminants.ts, 47, 16)) +>getArrayResult : Symbol(getArrayResult, Decl(controlFlowAliasedDiscriminants.ts, 42, 1)) + + const [foo3, bar3] = getArrayResult(); +>foo3 : Symbol(foo3, Decl(controlFlowAliasedDiscriminants.ts, 48, 11)) +>bar3 : Symbol(bar3, Decl(controlFlowAliasedDiscriminants.ts, 48, 16)) +>getArrayResult : Symbol(getArrayResult, Decl(controlFlowAliasedDiscriminants.ts, 42, 1)) + + const arrayAllSuccess = foo1 && foo2 && foo3; +>arrayAllSuccess : Symbol(arrayAllSuccess, Decl(controlFlowAliasedDiscriminants.ts, 49, 9)) +>foo1 : Symbol(foo1, Decl(controlFlowAliasedDiscriminants.ts, 46, 11)) +>foo2 : Symbol(foo2, Decl(controlFlowAliasedDiscriminants.ts, 47, 11)) +>foo3 : Symbol(foo3, Decl(controlFlowAliasedDiscriminants.ts, 48, 11)) + + if (arrayAllSuccess) { +>arrayAllSuccess : Symbol(arrayAllSuccess, Decl(controlFlowAliasedDiscriminants.ts, 49, 9)) + + bar1.toExponential(); // should ok +>bar1.toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) +>bar1 : Symbol(bar1, Decl(controlFlowAliasedDiscriminants.ts, 46, 16)) +>toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) + + bar2.toExponential(); // should ok +>bar2.toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) +>bar2 : Symbol(bar2, Decl(controlFlowAliasedDiscriminants.ts, 47, 16)) +>toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) + + bar3.toExponential(); // should ok +>bar3.toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) +>bar3 : Symbol(bar3, Decl(controlFlowAliasedDiscriminants.ts, 48, 16)) +>toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) + } +} + +{ + const [foo1, bar1] = getArrayResult(); +>foo1 : Symbol(foo1, Decl(controlFlowAliasedDiscriminants.ts, 58, 11)) +>bar1 : Symbol(bar1, Decl(controlFlowAliasedDiscriminants.ts, 58, 16)) +>getArrayResult : Symbol(getArrayResult, Decl(controlFlowAliasedDiscriminants.ts, 42, 1)) + + let [foo2, bar2] = getArrayResult(); +>foo2 : Symbol(foo2, Decl(controlFlowAliasedDiscriminants.ts, 59, 9)) +>bar2 : Symbol(bar2, Decl(controlFlowAliasedDiscriminants.ts, 59, 14)) +>getArrayResult : Symbol(getArrayResult, Decl(controlFlowAliasedDiscriminants.ts, 42, 1)) + + let [foo3, bar3] = getArrayResult(); +>foo3 : Symbol(foo3, Decl(controlFlowAliasedDiscriminants.ts, 60, 9)) +>bar3 : Symbol(bar3, Decl(controlFlowAliasedDiscriminants.ts, 60, 14)) +>getArrayResult : Symbol(getArrayResult, Decl(controlFlowAliasedDiscriminants.ts, 42, 1)) + + const arrayAllSuccess = foo1 && foo2 && foo3; +>arrayAllSuccess : Symbol(arrayAllSuccess, Decl(controlFlowAliasedDiscriminants.ts, 61, 9)) +>foo1 : Symbol(foo1, Decl(controlFlowAliasedDiscriminants.ts, 58, 11)) +>foo2 : Symbol(foo2, Decl(controlFlowAliasedDiscriminants.ts, 59, 9)) +>foo3 : Symbol(foo3, Decl(controlFlowAliasedDiscriminants.ts, 60, 9)) + + if (arrayAllSuccess) { +>arrayAllSuccess : Symbol(arrayAllSuccess, Decl(controlFlowAliasedDiscriminants.ts, 61, 9)) + + bar1.toExponential(); // should ok +>bar1.toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) +>bar1 : Symbol(bar1, Decl(controlFlowAliasedDiscriminants.ts, 58, 16)) +>toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) + + bar2.toExponential(); // should error +>bar2.toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) +>bar2 : Symbol(bar2, Decl(controlFlowAliasedDiscriminants.ts, 59, 14)) +>toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) + + bar3.toExponential(); // should error +>bar3.toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) +>bar3 : Symbol(bar3, Decl(controlFlowAliasedDiscriminants.ts, 60, 14)) +>toExponential : Symbol(Number.toExponential, Decl(lib.es5.d.ts, --, --)) + } +} + +type Nested = { +>Nested : Symbol(Nested, Decl(controlFlowAliasedDiscriminants.ts, 67, 1)) + + type: 'string'; +>type : Symbol(type, Decl(controlFlowAliasedDiscriminants.ts, 69, 15)) + + resp: { +>resp : Symbol(resp, Decl(controlFlowAliasedDiscriminants.ts, 70, 19)) + + data: string +>data : Symbol(data, Decl(controlFlowAliasedDiscriminants.ts, 71, 11)) + } +} | { + type: 'number'; +>type : Symbol(type, Decl(controlFlowAliasedDiscriminants.ts, 74, 5)) + + resp: { +>resp : Symbol(resp, Decl(controlFlowAliasedDiscriminants.ts, 75, 19)) + + data: number; +>data : Symbol(data, Decl(controlFlowAliasedDiscriminants.ts, 76, 11)) + } +} + +{ + let resp!: Nested; +>resp : Symbol(resp, Decl(controlFlowAliasedDiscriminants.ts, 82, 7)) +>Nested : Symbol(Nested, Decl(controlFlowAliasedDiscriminants.ts, 67, 1)) + + const { resp: { data }, type } = resp; +>resp : Symbol(resp, Decl(controlFlowAliasedDiscriminants.ts, 70, 19), Decl(controlFlowAliasedDiscriminants.ts, 75, 19)) +>data : Symbol(data, Decl(controlFlowAliasedDiscriminants.ts, 83, 19)) +>type : Symbol(type, Decl(controlFlowAliasedDiscriminants.ts, 83, 27)) +>resp : Symbol(resp, Decl(controlFlowAliasedDiscriminants.ts, 82, 7)) + + if (type === 'string') { +>type : Symbol(type, Decl(controlFlowAliasedDiscriminants.ts, 83, 27)) + + data satisfies string; +>data : Symbol(data, Decl(controlFlowAliasedDiscriminants.ts, 83, 19)) + } + if (resp.type === 'string') { +>resp.type : Symbol(type, Decl(controlFlowAliasedDiscriminants.ts, 69, 15), Decl(controlFlowAliasedDiscriminants.ts, 74, 5)) +>resp : Symbol(resp, Decl(controlFlowAliasedDiscriminants.ts, 82, 7)) +>type : Symbol(type, Decl(controlFlowAliasedDiscriminants.ts, 69, 15), Decl(controlFlowAliasedDiscriminants.ts, 74, 5)) + + resp.resp.data satisfies string; +>resp.resp.data : Symbol(data, Decl(controlFlowAliasedDiscriminants.ts, 71, 11)) +>resp.resp : Symbol(resp, Decl(controlFlowAliasedDiscriminants.ts, 70, 19)) +>resp : Symbol(resp, Decl(controlFlowAliasedDiscriminants.ts, 82, 7)) +>resp : Symbol(resp, Decl(controlFlowAliasedDiscriminants.ts, 70, 19)) +>data : Symbol(data, Decl(controlFlowAliasedDiscriminants.ts, 71, 11)) + } +} + +{ + + let resp!: Nested; +>resp : Symbol(resp, Decl(controlFlowAliasedDiscriminants.ts, 94, 7)) +>Nested : Symbol(Nested, Decl(controlFlowAliasedDiscriminants.ts, 67, 1)) + + const { resp: { data: dataAlias }, type } = resp; +>resp : Symbol(resp, Decl(controlFlowAliasedDiscriminants.ts, 70, 19), Decl(controlFlowAliasedDiscriminants.ts, 75, 19)) +>data : Symbol(data, Decl(controlFlowAliasedDiscriminants.ts, 71, 11), Decl(controlFlowAliasedDiscriminants.ts, 76, 11)) +>dataAlias : Symbol(dataAlias, Decl(controlFlowAliasedDiscriminants.ts, 95, 19)) +>type : Symbol(type, Decl(controlFlowAliasedDiscriminants.ts, 95, 38)) +>resp : Symbol(resp, Decl(controlFlowAliasedDiscriminants.ts, 94, 7)) + + if (type === 'string') { +>type : Symbol(type, Decl(controlFlowAliasedDiscriminants.ts, 95, 38)) + + dataAlias satisfies string; +>dataAlias : Symbol(dataAlias, Decl(controlFlowAliasedDiscriminants.ts, 95, 19)) + } + if (resp.type === 'string') { +>resp.type : Symbol(type, Decl(controlFlowAliasedDiscriminants.ts, 69, 15), Decl(controlFlowAliasedDiscriminants.ts, 74, 5)) +>resp : Symbol(resp, Decl(controlFlowAliasedDiscriminants.ts, 94, 7)) +>type : Symbol(type, Decl(controlFlowAliasedDiscriminants.ts, 69, 15), Decl(controlFlowAliasedDiscriminants.ts, 74, 5)) + + resp.resp.data satisfies string; +>resp.resp.data : Symbol(data, Decl(controlFlowAliasedDiscriminants.ts, 71, 11)) +>resp.resp : Symbol(resp, Decl(controlFlowAliasedDiscriminants.ts, 70, 19)) +>resp : Symbol(resp, Decl(controlFlowAliasedDiscriminants.ts, 94, 7)) +>resp : Symbol(resp, Decl(controlFlowAliasedDiscriminants.ts, 70, 19)) +>data : Symbol(data, Decl(controlFlowAliasedDiscriminants.ts, 71, 11)) + } +} + diff --git a/tests/baselines/reference/controlFlowAliasedDiscriminants.types b/tests/baselines/reference/controlFlowAliasedDiscriminants.types new file mode 100644 index 0000000000000..5925a680bcc89 --- /dev/null +++ b/tests/baselines/reference/controlFlowAliasedDiscriminants.types @@ -0,0 +1,378 @@ +//// [tests/cases/compiler/controlFlowAliasedDiscriminants.ts] //// + +=== controlFlowAliasedDiscriminants.ts === +type UseQueryResult = { +>UseQueryResult : UseQueryResult + + isSuccess: false; +>isSuccess : false +>false : false + + data: undefined; +>data : undefined + +} | { + isSuccess: true; +>isSuccess : true +>true : true + + data: T +>data : T + +}; + +function useQuery(): UseQueryResult { +>useQuery : () => UseQueryResult + + return { +>{ isSuccess: false, data: undefined, } : { isSuccess: false; data: undefined; } + + isSuccess: false, +>isSuccess : false +>false : false + + data: undefined, +>data : undefined +>undefined : undefined + + }; +} + +const { data: data1, isSuccess: isSuccess1 } = useQuery(); +>data : any +>data1 : number | undefined +>isSuccess : any +>isSuccess1 : boolean +>useQuery() : UseQueryResult +>useQuery : () => UseQueryResult + +const { data: data2, isSuccess: isSuccess2 } = useQuery(); +>data : any +>data2 : number | undefined +>isSuccess : any +>isSuccess2 : boolean +>useQuery() : UseQueryResult +>useQuery : () => UseQueryResult + +const { data: data3, isSuccess: isSuccess3 } = useQuery(); +>data : any +>data3 : number | undefined +>isSuccess : any +>isSuccess3 : boolean +>useQuery() : UseQueryResult +>useQuery : () => UseQueryResult + +if (isSuccess1 && isSuccess2 && isSuccess3) { +>isSuccess1 && isSuccess2 && isSuccess3 : boolean +>isSuccess1 && isSuccess2 : boolean +>isSuccess1 : boolean +>isSuccess2 : boolean +>isSuccess3 : boolean + + data1.toExponential(); // should ok +>data1.toExponential() : string +>data1.toExponential : (fractionDigits?: number | undefined) => string +>data1 : number +>toExponential : (fractionDigits?: number | undefined) => string + + data2.toExponential(); // should ok +>data2.toExponential() : string +>data2.toExponential : (fractionDigits?: number | undefined) => string +>data2 : number +>toExponential : (fractionDigits?: number | undefined) => string + + data3.toExponential(); // should ok +>data3.toExponential() : string +>data3.toExponential : (fractionDigits?: number | undefined) => string +>data3 : number +>toExponential : (fractionDigits?: number | undefined) => string +} + +const areSuccess = isSuccess1 && isSuccess2 && isSuccess3; +>areSuccess : boolean +>isSuccess1 && isSuccess2 && isSuccess3 : boolean +>isSuccess1 && isSuccess2 : boolean +>isSuccess1 : boolean +>isSuccess2 : boolean +>isSuccess3 : boolean + +if (areSuccess) { +>areSuccess : boolean + + data1.toExponential(); // should ok +>data1.toExponential() : string +>data1.toExponential : (fractionDigits?: number | undefined) => string +>data1 : number +>toExponential : (fractionDigits?: number | undefined) => string + + data2.toExponential(); // should ok +>data2.toExponential() : string +>data2.toExponential : (fractionDigits?: number | undefined) => string +>data2 : number +>toExponential : (fractionDigits?: number | undefined) => string + + data3.toExponential(); // should ok +>data3.toExponential() : string +>data3.toExponential : (fractionDigits?: number | undefined) => string +>data3 : number +>toExponential : (fractionDigits?: number | undefined) => string +} + +{ + let { data: data1, isSuccess: isSuccess1 } = useQuery(); +>data : any +>data1 : number | undefined +>isSuccess : any +>isSuccess1 : boolean +>useQuery() : UseQueryResult +>useQuery : () => UseQueryResult + + let { data: data2, isSuccess: isSuccess2 } = useQuery(); +>data : any +>data2 : number | undefined +>isSuccess : any +>isSuccess2 : boolean +>useQuery() : UseQueryResult +>useQuery : () => UseQueryResult + + const { data: data3, isSuccess: isSuccess3 } = useQuery(); +>data : any +>data3 : number | undefined +>isSuccess : any +>isSuccess3 : boolean +>useQuery() : UseQueryResult +>useQuery : () => UseQueryResult + + const areSuccess = isSuccess1 && isSuccess2 && isSuccess3; +>areSuccess : boolean +>isSuccess1 && isSuccess2 && isSuccess3 : boolean +>isSuccess1 && isSuccess2 : boolean +>isSuccess1 : boolean +>isSuccess2 : boolean +>isSuccess3 : boolean + + if (areSuccess) { +>areSuccess : boolean + + data1.toExponential(); // should error +>data1.toExponential() : string +>data1.toExponential : (fractionDigits?: number | undefined) => string +>data1 : number | undefined +>toExponential : (fractionDigits?: number | undefined) => string + + data2.toExponential(); // should error +>data2.toExponential() : string +>data2.toExponential : (fractionDigits?: number | undefined) => string +>data2 : number | undefined +>toExponential : (fractionDigits?: number | undefined) => string + + data3.toExponential(); // should ok +>data3.toExponential() : string +>data3.toExponential : (fractionDigits?: number | undefined) => string +>data3 : number +>toExponential : (fractionDigits?: number | undefined) => string + } +} + +declare function getArrayResult(): [true, number] | [false, undefined]; +>getArrayResult : () => [true, number] | [false, undefined] +>true : true +>false : false +{ + const [foo1, bar1] = getArrayResult(); +>foo1 : boolean +>bar1 : number | undefined +>getArrayResult() : [true, number] | [false, undefined] +>getArrayResult : () => [true, number] | [false, undefined] + + const [foo2, bar2] = getArrayResult(); +>foo2 : boolean +>bar2 : number | undefined +>getArrayResult() : [true, number] | [false, undefined] +>getArrayResult : () => [true, number] | [false, undefined] + + const [foo3, bar3] = getArrayResult(); +>foo3 : boolean +>bar3 : number | undefined +>getArrayResult() : [true, number] | [false, undefined] +>getArrayResult : () => [true, number] | [false, undefined] + + const arrayAllSuccess = foo1 && foo2 && foo3; +>arrayAllSuccess : boolean +>foo1 && foo2 && foo3 : boolean +>foo1 && foo2 : boolean +>foo1 : boolean +>foo2 : boolean +>foo3 : boolean + + if (arrayAllSuccess) { +>arrayAllSuccess : boolean + + bar1.toExponential(); // should ok +>bar1.toExponential() : string +>bar1.toExponential : (fractionDigits?: number | undefined) => string +>bar1 : number +>toExponential : (fractionDigits?: number | undefined) => string + + bar2.toExponential(); // should ok +>bar2.toExponential() : string +>bar2.toExponential : (fractionDigits?: number | undefined) => string +>bar2 : number +>toExponential : (fractionDigits?: number | undefined) => string + + bar3.toExponential(); // should ok +>bar3.toExponential() : string +>bar3.toExponential : (fractionDigits?: number | undefined) => string +>bar3 : number +>toExponential : (fractionDigits?: number | undefined) => string + } +} + +{ + const [foo1, bar1] = getArrayResult(); +>foo1 : boolean +>bar1 : number | undefined +>getArrayResult() : [true, number] | [false, undefined] +>getArrayResult : () => [true, number] | [false, undefined] + + let [foo2, bar2] = getArrayResult(); +>foo2 : boolean +>bar2 : number | undefined +>getArrayResult() : [true, number] | [false, undefined] +>getArrayResult : () => [true, number] | [false, undefined] + + let [foo3, bar3] = getArrayResult(); +>foo3 : boolean +>bar3 : number | undefined +>getArrayResult() : [true, number] | [false, undefined] +>getArrayResult : () => [true, number] | [false, undefined] + + const arrayAllSuccess = foo1 && foo2 && foo3; +>arrayAllSuccess : boolean +>foo1 && foo2 && foo3 : boolean +>foo1 && foo2 : boolean +>foo1 : boolean +>foo2 : boolean +>foo3 : boolean + + if (arrayAllSuccess) { +>arrayAllSuccess : boolean + + bar1.toExponential(); // should ok +>bar1.toExponential() : string +>bar1.toExponential : (fractionDigits?: number | undefined) => string +>bar1 : number +>toExponential : (fractionDigits?: number | undefined) => string + + bar2.toExponential(); // should error +>bar2.toExponential() : string +>bar2.toExponential : (fractionDigits?: number | undefined) => string +>bar2 : number | undefined +>toExponential : (fractionDigits?: number | undefined) => string + + bar3.toExponential(); // should error +>bar3.toExponential() : string +>bar3.toExponential : (fractionDigits?: number | undefined) => string +>bar3 : number | undefined +>toExponential : (fractionDigits?: number | undefined) => string + } +} + +type Nested = { +>Nested : { type: 'string'; resp: { data: string;}; } | { type: 'number'; resp: { data: number;}; } + + type: 'string'; +>type : "string" + + resp: { +>resp : { data: string; } + + data: string +>data : string + } +} | { + type: 'number'; +>type : "number" + + resp: { +>resp : { data: number; } + + data: number; +>data : number + } +} + +{ + let resp!: Nested; +>resp : Nested + + const { resp: { data }, type } = resp; +>resp : any +>data : string | number +>type : "string" | "number" +>resp : Nested + + if (type === 'string') { +>type === 'string' : boolean +>type : "string" | "number" +>'string' : "string" + + data satisfies string; +>data satisfies string : string | number +>data : string | number + } + if (resp.type === 'string') { +>resp.type === 'string' : boolean +>resp.type : "string" | "number" +>resp : Nested +>type : "string" | "number" +>'string' : "string" + + resp.resp.data satisfies string; +>resp.resp.data satisfies string : string +>resp.resp.data : string +>resp.resp : { data: string; } +>resp : { type: "string"; resp: { data: string; }; } +>resp : { data: string; } +>data : string + } +} + +{ + + let resp!: Nested; +>resp : Nested + + const { resp: { data: dataAlias }, type } = resp; +>resp : any +>data : any +>dataAlias : string | number +>type : "string" | "number" +>resp : Nested + + if (type === 'string') { +>type === 'string' : boolean +>type : "string" | "number" +>'string' : "string" + + dataAlias satisfies string; +>dataAlias satisfies string : string | number +>dataAlias : string | number + } + if (resp.type === 'string') { +>resp.type === 'string' : boolean +>resp.type : "string" | "number" +>resp : Nested +>type : "string" | "number" +>'string' : "string" + + resp.resp.data satisfies string; +>resp.resp.data satisfies string : string +>resp.resp.data : string +>resp.resp : { data: string; } +>resp : { type: "string"; resp: { data: string; }; } +>resp : { data: string; } +>data : string + } +} + diff --git a/tests/cases/compiler/controlFlowAliasedDiscriminants.ts b/tests/cases/compiler/controlFlowAliasedDiscriminants.ts new file mode 100644 index 0000000000000..53e7bdd8bd352 --- /dev/null +++ b/tests/cases/compiler/controlFlowAliasedDiscriminants.ts @@ -0,0 +1,106 @@ +// @strictNullChecks: true +// @noImplicitAny: true + +type UseQueryResult = { + isSuccess: false; + data: undefined; +} | { + isSuccess: true; + data: T +}; + +function useQuery(): UseQueryResult { + return { + isSuccess: false, + data: undefined, + }; +} + +const { data: data1, isSuccess: isSuccess1 } = useQuery(); +const { data: data2, isSuccess: isSuccess2 } = useQuery(); +const { data: data3, isSuccess: isSuccess3 } = useQuery(); + +if (isSuccess1 && isSuccess2 && isSuccess3) { + data1.toExponential(); // should ok + data2.toExponential(); // should ok + data3.toExponential(); // should ok +} + +const areSuccess = isSuccess1 && isSuccess2 && isSuccess3; +if (areSuccess) { + data1.toExponential(); // should ok + data2.toExponential(); // should ok + data3.toExponential(); // should ok +} + +{ + let { data: data1, isSuccess: isSuccess1 } = useQuery(); + let { data: data2, isSuccess: isSuccess2 } = useQuery(); + const { data: data3, isSuccess: isSuccess3 } = useQuery(); + const areSuccess = isSuccess1 && isSuccess2 && isSuccess3; + if (areSuccess) { + data1.toExponential(); // should error + data2.toExponential(); // should error + data3.toExponential(); // should ok + } +} + +declare function getArrayResult(): [true, number] | [false, undefined]; +{ + const [foo1, bar1] = getArrayResult(); + const [foo2, bar2] = getArrayResult(); + const [foo3, bar3] = getArrayResult(); + const arrayAllSuccess = foo1 && foo2 && foo3; + if (arrayAllSuccess) { + bar1.toExponential(); // should ok + bar2.toExponential(); // should ok + bar3.toExponential(); // should ok + } +} + +{ + const [foo1, bar1] = getArrayResult(); + let [foo2, bar2] = getArrayResult(); + let [foo3, bar3] = getArrayResult(); + const arrayAllSuccess = foo1 && foo2 && foo3; + if (arrayAllSuccess) { + bar1.toExponential(); // should ok + bar2.toExponential(); // should error + bar3.toExponential(); // should error + } +} + +type Nested = { + type: 'string'; + resp: { + data: string + } +} | { + type: 'number'; + resp: { + data: number; + } +} + +{ + let resp!: Nested; + const { resp: { data }, type } = resp; + if (type === 'string') { + data satisfies string; + } + if (resp.type === 'string') { + resp.resp.data satisfies string; + } +} + +{ + + let resp!: Nested; + const { resp: { data: dataAlias }, type } = resp; + if (type === 'string') { + dataAlias satisfies string; + } + if (resp.type === 'string') { + resp.resp.data satisfies string; + } +} From 8e1fb5789abcd93592d8e437df8f7261295613fc Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Thu, 9 Nov 2023 06:14:48 +0000 Subject: [PATCH 4/7] Update package-lock.json --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 993d7ca04ca3c..2e2470647d2f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2651,9 +2651,9 @@ "dev": true }, "node_modules/istanbul-lib-coverage": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.1.tgz", - "integrity": "sha512-opCrKqbthmq3SKZ10mFMQG9dk3fTa3quaOLD35kJa5ejwZHd9xAr+kLuziiZz2cG32s4lMZxNdmdcEQnTDP4+g==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "engines": { "node": ">=8" @@ -5786,9 +5786,9 @@ "dev": true }, "istanbul-lib-coverage": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.1.tgz", - "integrity": "sha512-opCrKqbthmq3SKZ10mFMQG9dk3fTa3quaOLD35kJa5ejwZHd9xAr+kLuziiZz2cG32s4lMZxNdmdcEQnTDP4+g==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true }, "istanbul-lib-report": { From efecc85dcae682d76a3884149b3e1eb97a31774f Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 9 Nov 2023 11:48:48 -0800 Subject: [PATCH 5/7] All tsserver unittests use session and typing installer (#56337) --- src/harness/fourslashImpl.ts | 6 +- src/harness/harnessLanguageService.ts | 6 +- src/harness/incrementalUtils.ts | 1 + src/harness/tsserverLogger.ts | 63 +- src/server/session.ts | 5 +- src/testRunner/unittests/helpers/tscWatch.ts | 71 +- src/testRunner/unittests/helpers/tsserver.ts | 210 +- .../unittests/helpers/typingsInstaller.ts | 350 +- .../helpers/virtualFileSystemWithWatch.ts | 152 +- .../services/convertToAsyncFunction.ts | 6 +- .../unittests/services/extract/helpers.ts | 39 +- .../unittests/services/organizeImports.ts | 6 +- src/testRunner/unittests/tsbuild/sample.ts | 20 +- .../unittests/tsbuildWatch/programUpdates.ts | 3 +- .../unittests/tsbuildWatch/publicApi.ts | 3 +- .../tsbuildWatch/watchEnvironment.ts | 5 +- .../unittests/tsc/cancellationToken.ts | 9 +- .../unittests/tscWatch/incremental.ts | 12 +- .../unittests/tscWatch/programUpdates.ts | 11 +- .../tscWatch/projectsWithReferences.ts | 5 +- .../unittests/tscWatch/resolutionCache.ts | 14 +- .../sourceOfProjectReferenceRedirect.ts | 3 +- src/testRunner/unittests/tscWatch/watchApi.ts | 39 +- .../unittests/tscWatch/watchEnvironment.ts | 12 +- .../tsserver/applyChangesToOpenFiles.ts | 7 +- .../unittests/tsserver/autoImportProvider.ts | 7 +- .../unittests/tsserver/auxiliaryProject.ts | 16 +- .../tsserver/cachingFileSystemInformation.ts | 137 +- .../unittests/tsserver/cancellationToken.ts | 46 +- .../unittests/tsserver/compileOnSave.ts | 65 +- .../unittests/tsserver/completions.ts | 13 +- .../tsserver/completionsIncomplete.ts | 7 +- .../unittests/tsserver/configFileSearch.ts | 59 +- .../unittests/tsserver/configuredProjects.ts | 321 +- .../unittests/tsserver/declarationFileMaps.ts | 12 +- .../unittests/tsserver/documentRegistry.ts | 91 +- .../unittests/tsserver/duplicatePackages.ts | 7 +- .../unittests/tsserver/dynamicFiles.ts | 111 +- .../tsserver/events/largeFileReferenced.ts | 8 +- .../events/projectLanguageServiceState.ts | 20 +- .../tsserver/events/projectLoading.ts | 12 +- .../events/projectUpdatedInBackground.ts | 17 +- .../unittests/tsserver/events/watchEvents.ts | 17 +- .../unittests/tsserver/exportMapCache.ts | 22 +- src/testRunner/unittests/tsserver/extends.ts | 7 +- .../unittests/tsserver/externalProjects.ts | 393 ++- .../unittests/tsserver/findAllReferences.ts | 7 +- .../forceConsistentCasingInFileNames.ts | 11 +- .../unittests/tsserver/formatSettings.ts | 7 +- .../tsserver/getApplicableRefactors.ts | 7 +- .../tsserver/getEditsForFileRename.ts | 9 +- .../unittests/tsserver/getExportReferences.ts | 7 +- .../unittests/tsserver/getFileReferences.ts | 7 +- .../getMoveToRefactoringFileSuggestions.ts | 13 +- .../unittests/tsserver/goToDefinition.ts | 9 +- .../unittests/tsserver/importHelpers.ts | 13 +- .../tsserver/inconsistentErrorInEditor.ts | 9 +- .../unittests/tsserver/inferredProjects.ts | 80 +- .../unittests/tsserver/inlayHints.ts | 6 +- src/testRunner/unittests/tsserver/jsdocTag.ts | 11 +- .../unittests/tsserver/languageService.ts | 32 +- .../unittests/tsserver/libraryResolution.ts | 9 +- .../tsserver/maxNodeModuleJsDepth.ts | 13 +- .../unittests/tsserver/metadataInResponse.ts | 11 +- .../unittests/tsserver/moduleResolution.ts | 11 +- .../tsserver/moduleSpecifierCache.ts | 12 +- src/testRunner/unittests/tsserver/navTo.ts | 13 +- .../unittests/tsserver/occurences.ts | 7 +- src/testRunner/unittests/tsserver/openFile.ts | 76 +- .../unittests/tsserver/packageJsonInfo.ts | 19 +- .../tsserver/partialSemanticServer.ts | 19 +- src/testRunner/unittests/tsserver/plugins.ts | 18 +- .../unittests/tsserver/pluginsAsync.ts | 7 +- .../unittests/tsserver/projectErrors.ts | 63 +- .../tsserver/projectReferenceCompileOnSave.ts | 97 +- .../unittests/tsserver/projectReferences.ts | 44 +- .../tsserver/projectReferencesSourcemap.ts | 16 +- src/testRunner/unittests/tsserver/projects.ts | 560 ++-- .../tsserver/projectsWithReferences.ts | 69 +- .../unittests/tsserver/refactors.ts | 17 +- src/testRunner/unittests/tsserver/reload.ts | 16 +- .../unittests/tsserver/reloadProjects.ts | 18 +- src/testRunner/unittests/tsserver/rename.ts | 13 +- .../unittests/tsserver/resolutionCache.ts | 134 +- src/testRunner/unittests/tsserver/session.ts | 1 - .../unittests/tsserver/skipLibCheck.ts | 19 +- .../unittests/tsserver/smartSelection.ts | 7 +- src/testRunner/unittests/tsserver/symLinks.ts | 11 +- .../unittests/tsserver/symlinkCache.ts | 7 +- .../unittests/tsserver/syntacticServer.ts | 8 +- .../unittests/tsserver/syntaxOperations.ts | 7 +- .../unittests/tsserver/telemetry.ts | 65 +- .../unittests/tsserver/textStorage.ts | 14 +- .../unittests/tsserver/typeAquisition.ts | 35 +- .../tsserver/typeOnlyImportChains.ts | 7 +- .../tsserver/typeReferenceDirectives.ts | 9 +- .../unittests/tsserver/typingsInstaller.ts | 847 ++--- .../unittests/tsserver/watchEnvironment.ts | 75 +- src/typingsInstaller/nodeTypingsInstaller.ts | 27 +- src/typingsInstallerCore/typingsInstaller.ts | 40 +- .../extends/resolves-the-symlink-path.js | 48 +- ...project-correctly-with-preserveSymlinks.js | 116 +- ...-file-from-referenced-project-correctly.js | 112 +- ...uilding-using-getNextInvalidatedProject.js | 10 - .../sample1/invalidates-projects-correctly.js | 10 - .../reports-syntax-errors-in-config-file.js | 182 +- .../demo/updates-with-bad-reference.js | 233 +- .../demo/updates-with-circular-reference.js | 272 +- .../with-config-with-redirection.js | 458 +-- .../libraryResolution/with-config.js | 458 +-- ...for-changes-to-package-json-main-fields.js | 184 +- ...t-correctly-with-cts-and-mts-extensions.js | 495 +-- ...se-different-module-resolution-settings.js | 221 +- ...n-no-files-are-emitted-with-incremental.js | 150 +- ...when-watching-when-no-files-are-emitted.js | 40 +- ...mit-any-files-on-error-with-incremental.js | 290 +- .../does-not-emit-any-files-on-error.js | 132 +- .../creates-solution-in-watch-mode.js | 244 +- .../incremental-updates-in-verbose-mode.js | 437 +-- .../when-file-with-no-error-changes.js | 180 +- ...ing-errors-only-changed-file-is-emitted.js | 174 +- .../when-file-with-no-error-changes.js | 127 +- ...ixing-error-files-all-files-are-emitted.js | 123 +- .../when-preserveWatchOutput-is-not-used.js | 362 +- ...veWatchOutput-is-passed-on-command-line.js | 368 +- ...e-of-program-emit-with-outDir-specified.js | 193 +- ...r-recompilation-because-of-program-emit.js | 199 +- .../programUpdates/tsbuildinfo-has-error.js | 66 +- ...-references-watches-only-those-projects.js | 170 +- ...tches-config-files-that-are-not-present.js | 275 +- ...e-down-stream-project-and-then-fixes-it.js | 284 +- ...ncing-project-even-for-non-local-change.js | 244 +- ...le-is-added,-and-its-subsequent-updates.js | 414 +-- ...hanges-and-reports-found-errors-message.js | 709 ++-- ...not-start-build-of-referencing-projects.js | 287 +- ...le-is-added,-and-its-subsequent-updates.js | 422 +-- ...hanges-and-reports-found-errors-message.js | 711 ++-- ...not-start-build-of-referencing-projects.js | 291 +- ...project-with-extended-config-is-removed.js | 143 +- ...hen-noUnusedParameters-changes-to-false.js | 37 +- .../works-with-extended-source-files.js | 587 ++-- ...hen-there-are-23-projects-in-a-solution.js | 2977 +++++++++-------- ...when-there-are-3-projects-in-a-solution.js | 292 +- ...when-there-are-5-projects-in-a-solution.js | 404 +-- ...when-there-are-8-projects-in-a-solution.js | 713 ++-- .../publicApi/with-custom-transformers.js | 204 +- .../reexport/Reports-errors-correctly.js | 320 +- ...e-projects-with-single-watcher-per-file.js | 198 +- .../when-emitting-buildInfo.js | 146 +- .../tsc/cancellationToken/when-using-state.js | 148 +- ...rough-indirect-symlink-moduleCaseChange.js | 56 +- ...ibling-package-through-indirect-symlink.js | 56 +- ...ther-symlinked-package-moduleCaseChange.js | 46 +- ...age-with-indirect-link-moduleCaseChange.js | 38 +- ...er-symlinked-package-with-indirect-link.js | 38 +- ...gh-source-and-another-symlinked-package.js | 46 +- .../tsc/extends/resolves-the-symlink-path.js | 50 +- .../createWatchOfConfigFile.js | 47 +- ...Result-on-WatchCompilerHostOfConfigFile.js | 47 +- .../consoleClearing/with---diagnostics.js | 44 +- .../with---extendedDiagnostics.js | 44 +- .../with---preserveWatchOutput.js | 35 +- ...---diagnostics-or---extendedDiagnostics.js | 35 +- ...ms-correctly-in-incremental-compilation.js | 75 +- ...s-deleted-and-created-as-part-of-change.js | 57 +- ...ndles-new-lines-carriageReturn-lineFeed.js | 43 +- .../handles-new-lines-lineFeed.js | 43 +- .../should-emit-specified-file.js | 144 +- ...elf-if-'--isolatedModules'-is-specified.js | 131 +- ...-if-'--out'-or-'--outFile'-is-specified.js | 137 +- ...should-be-up-to-date-with-deleted-files.js | 177 +- ...-be-up-to-date-with-newly-created-files.js | 193 +- ...-to-date-with-the-reference-map-changes.js | 229 +- ...les-referencing-it-if-its-shape-changed.js | 160 +- ...should-detect-changes-in-non-root-files.js | 108 +- .../should-detect-non-existing-code-file.js | 126 +- .../should-detect-removed-code-file.js | 109 +- ...ll-files-if-a-global-file-changed-shape.js | 129 +- ...ould-return-cascaded-affected-file-list.js | 215 +- ...fine-for-files-with-circular-references.js | 89 +- .../config-does-not-have-out-or-outFile.js | 80 +- .../config-has-out.js | 74 +- .../config-has-outFile.js | 74 +- ...ltiple-declaration-files-in-the-program.js | 70 +- ...ltiple-declaration-files-in-the-program.js | 76 +- ...-recursive-directory-watcher-is-invoked.js | 71 +- ...rrors-for-.d.ts-change-with-incremental.js | 249 +- .../errors-for-.d.ts-change.js | 87 +- .../errors-for-.ts-change-with-incremental.js | 277 +- .../errors-for-.ts-change.js | 213 +- ...el-import-that-changes-with-incremental.js | 299 +- ...g-a-deep-multilevel-import-that-changes.js | 169 +- .../export-with-incremental.js | 299 +- .../no-circular-import/export.js | 167 +- .../exports-with-incremental.js | 311 +- .../yes-circular-import/exports.js | 179 +- .../with-noEmitOnError-with-incremental.js | 306 +- .../with-noEmitOnError.js | 138 +- ...rrors-for-.d.ts-change-with-incremental.js | 251 +- .../errors-for-.d.ts-change.js | 95 +- .../errors-for-.ts-change-with-incremental.js | 247 +- .../errors-for-.ts-change.js | 245 +- ...el-import-that-changes-with-incremental.js | 347 +- ...g-a-deep-multilevel-import-that-changes.js | 237 +- .../export-with-incremental.js | 263 +- .../no-circular-import/export.js | 203 +- .../exports-with-incremental.js | 273 +- .../yes-circular-import/exports.js | 213 +- .../with-noEmitOnError-with-incremental.js | 312 +- .../with-noEmitOnError.js | 156 +- ...rrors-for-.d.ts-change-with-incremental.js | 267 +- .../errors-for-.d.ts-change.js | 85 +- .../errors-for-.ts-change-with-incremental.js | 267 +- .../errors-for-.ts-change.js | 211 +- ...el-import-that-changes-with-incremental.js | 323 +- ...g-a-deep-multilevel-import-that-changes.js | 167 +- .../export-with-incremental.js | 295 +- .../no-circular-import/export.js | 165 +- .../exports-with-incremental.js | 309 +- .../yes-circular-import/exports.js | 177 +- .../with-noEmitOnError-with-incremental.js | 300 +- .../default/with-noEmitOnError.js | 138 +- ...rrors-for-.d.ts-change-with-incremental.js | 269 +- .../errors-for-.d.ts-change.js | 101 +- .../errors-for-.ts-change-with-incremental.js | 265 +- .../errors-for-.ts-change.js | 257 +- ...el-import-that-changes-with-incremental.js | 317 +- ...g-a-deep-multilevel-import-that-changes.js | 247 +- .../export-with-incremental.js | 281 +- .../no-circular-import/export.js | 225 +- .../exports-with-incremental.js | 293 +- .../yes-circular-import/exports.js | 241 +- .../with-noEmitOnError-with-incremental.js | 306 +- .../defaultAndD/with-noEmitOnError.js | 154 +- ...rrors-for-.d.ts-change-with-incremental.js | 269 +- .../errors-for-.d.ts-change.js | 87 +- .../errors-for-.ts-change-with-incremental.js | 263 +- .../errors-for-.ts-change.js | 205 +- ...el-import-that-changes-with-incremental.js | 319 +- ...g-a-deep-multilevel-import-that-changes.js | 157 +- .../export-with-incremental.js | 279 +- .../no-circular-import/export.js | 153 +- .../exports-with-incremental.js | 291 +- .../yes-circular-import/exports.js | 163 +- .../with-noEmitOnError-with-incremental.js | 306 +- .../isolatedModules/with-noEmitOnError.js | 138 +- ...rrors-for-.d.ts-change-with-incremental.js | 271 +- .../errors-for-.d.ts-change.js | 101 +- .../errors-for-.ts-change-with-incremental.js | 265 +- .../errors-for-.ts-change.js | 255 +- ...el-import-that-changes-with-incremental.js | 323 +- ...g-a-deep-multilevel-import-that-changes.js | 249 +- .../export-with-incremental.js | 281 +- .../no-circular-import/export.js | 221 +- .../exports-with-incremental.js | 293 +- .../yes-circular-import/exports.js | 237 +- .../with-noEmitOnError-with-incremental.js | 312 +- .../isolatedModulesAndD/with-noEmitOnError.js | 156 +- .../extends/resolves-the-symlink-path.js | 100 +- .../jsxImportSource-option-changed.js | 62 +- .../package-json-is-looked-up-for-file.js | 66 +- .../self-name-package-reference.js | 98 +- ...n-Windows-style-drive-root-is-lowercase.js | 99 +- ...n-Windows-style-drive-root-is-uppercase.js | 99 +- ...ry-symlink-target-and-import-match-disk.js | 153 +- ...le-symlink-target-and-import-match-disk.js | 123 +- ...nging-module-name-with-different-casing.js | 101 +- ...target-matches-disk-but-import-does-not.js | 153 +- ...target-matches-disk-but-import-does-not.js | 123 +- ...link-target,-and-disk-are-all-different.js | 157 +- ...link-target,-and-disk-are-all-different.js | 129 +- ...link-target-agree-but-do-not-match-disk.js | 153 +- ...link-target-agree-but-do-not-match-disk.js | 123 +- ...k-but-directory-symlink-target-does-not.js | 153 +- ...s-disk-but-file-symlink-target-does-not.js | 123 +- ...ative-information-file-location-changes.js | 89 +- ...hen-renaming-file-with-different-casing.js | 87 +- .../with-nodeNext-resolution.js | 50 +- ...editing-module-augmentation-incremental.js | 100 +- .../editing-module-augmentation-watch.js | 210 +- ...lpers-backing-types-removed-incremental.js | 90 +- ...portHelpers-backing-types-removed-watch.js | 100 +- ...al-with-circular-references-incremental.js | 110 +- ...remental-with-circular-references-watch.js | 220 +- ...tSource-backing-types-added-incremental.js | 102 +- ...xImportSource-backing-types-added-watch.js | 198 +- ...ource-backing-types-removed-incremental.js | 94 +- ...mportSource-backing-types-removed-watch.js | 196 +- ...ImportSource-option-changed-incremental.js | 114 +- .../jsxImportSource-option-changed-watch.js | 214 +- .../own-file-emit-with-errors-incremental.js | 92 +- .../own-file-emit-with-errors-watch.js | 180 +- ...wn-file-emit-without-errors-incremental.js | 76 +- .../own-file-emit-without-errors-watch.js | 170 +- .../with---out-incremental.js | 44 +- .../module-compilation/with---out-watch.js | 86 +- .../own-file-emit-with-errors-incremental.js | 92 +- .../own-file-emit-with-errors-watch.js | 180 +- ...eters-that-are-not-relative-incremental.js | 78 +- ...-parameters-that-are-not-relative-watch.js | 172 +- ...without-commandline-options-incremental.js | 76 +- .../without-commandline-options-watch.js | 170 +- .../incremental/tsbuildinfo-has-error.js | 72 +- ...declaration-file-is-deleted-incremental.js | 88 +- ...lobal-declaration-file-is-deleted-watch.js | 172 +- .../incremental/with---out-incremental.js | 42 +- .../tscWatch/incremental/with---out-watch.js | 84 +- .../with-config-with-redirection.js | 1688 +++++----- .../tscWatch/libraryResolution/with-config.js | 1678 +++++----- .../without-config-with-redirection.js | 590 ++-- .../libraryResolution/without-config.js | 594 ++-- .../diagnostics-from-cache.js | 107 +- ...esolutions-from-file-are-partially-used.js | 77 +- ...s-with-partially-used-import-attributes.js | 77 +- .../tscWatch/moduleResolution/node10Result.js | 1000 +++--- ...en-package-json-with-type-module-exists.js | 437 ++- .../package-json-file-is-edited.js | 447 ++- .../type-reference-resolutions-reuse.js | 97 +- ...for-changes-to-package-json-main-fields.js | 192 +- .../esm-mode-file-is-edited.js | 49 +- ...nerated-when-the-config-file-has-errors.js | 34 +- ...configFile-contents-when-options-change.js | 41 +- ...rs-document-is-not-contained-in-project.js | 32 +- ...rts-errors-when-the-config-file-changes.js | 48 +- ...en-'--allowArbitraryExtensions'-changes.js | 94 +- ...nostics-when-'--noUnusedLabels'-changes.js | 48 +- ...-a-configured-program-without-file-list.js | 79 +- ...hould-remove-the-module-not-found-error.js | 91 +- ...has-changed-(new-file-in-list-of-files).js | 63 +- ...ot-files-has-changed-(new-file-on-disk).js | 79 +- ...-root-files-has-changed-through-include.js | 101 +- ...config-file-name-with-difference-casing.js | 26 +- ...-when-set-of-root-files-was-not-changed.js | 55 +- .../programUpdates/change-module-to-none.js | 45 +- ...iles-are-reflected-in-project-structure.js | 143 +- .../config-file-includes-the-file.js | 64 +- .../programUpdates/config-file-is-deleted.js | 56 +- ...s-changes-in-lib-section-of-config-file.js | 71 +- ...keys-differ-only-in-directory-seperator.js | 262 +- ...te-configured-project-without-file-list.js | 46 +- .../create-watch-without-config-file.js | 40 +- ...eleted-files-affect-project-structure-2.js | 119 +- .../deleted-files-affect-project-structure.js | 117 +- .../extended-source-files-are-watched.js | 162 +- .../file-in-files-is-deleted.js | 79 +- ...iles-explicitly-excluded-in-config-file.js | 46 +- .../handle-recreated-files-correctly.js | 151 +- ...se-they-were-added-with-tripleSlashRefs.js | 65 +- ...esnt-have-errors,-they-are-not-reported.js | 34 +- ...ndle-@types-if-input-file-list-is-empty.js | 10 +- ...e-tolerated-without-crashing-the-server.js | 26 +- ...tore-the-states-for-configured-projects.js | 214 +- ...estore-the-states-for-inferred-projects.js | 150 +- ...rors-correctly-with-file-not-in-rootDir.js | 75 +- ...s-errors-correctly-with-isolatedModules.js | 91 +- ...non-existing-directories-in-config-file.js | 42 +- ...ting-files-specified-in-the-config-file.js | 34 +- .../declarationDir-is-specified.js | 180 +- ...-outDir-and-declarationDir-is-specified.js | 182 +- .../when-outDir-is-specified.js | 156 +- .../with-outFile.js | 158 +- ...e-is-specified-with-declaration-enabled.js | 178 +- .../without-outDir-or-outFile-is-specified.js | 152 +- ...odule-resolution-changes-in-config-file.js | 79 +- .../should-reflect-change-in-config-file.js | 90 +- ...should-support-files-without-extensions.js | 22 +- ...errors-and-still-try-to-build-a-project.js | 46 +- ...when-file-changes-from-global-to-module.js | 65 +- ...programs-are-not-affected-by-each-other.js | 112 +- ...-from-config-file-path-if-config-exists.js | 38 +- ...exists-but-does-not-specifies-typeRoots.js | 34 +- ...tes-diagnostics-and-emit-for-decorators.js | 174 +- ...it-when-useDefineForClassFields-changes.js | 91 +- .../updates-emit-on-jsx-option-add.js | 55 +- .../updates-emit-on-jsx-option-change.js | 57 +- ...mit-when-importsNotUsedAsValues-changes.js | 129 +- ...on-emit-is-disabled-in-compiler-options.js | 70 +- .../with-default-options.js | 54 +- .../with-skipDefaultLibCheck.js | 54 +- .../with-skipLibCheck.js | 54 +- .../with-default-options.js | 66 +- .../with-skipDefaultLibCheck.js | 66 +- .../with-skipLibCheck.js | 66 +- ...when-ambient-modules-of-program-changes.js | 138 +- ...orceConsistentCasingInFileNames-changes.js | 71 +- ...s-errors-when-noErrorTruncation-changes.js | 47 +- ...es-errors-when-strictNullChecks-changes.js | 61 +- ...solution-when-resolveJsonModule-changes.js | 97 +- ...and-new-file-is-added-as-part-of-change.js | 87 +- ...owImportingTsExtensions`-of-config-file.js | 58 +- .../when-changing-checkJs-of-config-file.js | 110 +- .../when-creating-extensionless-file.js | 58 +- ...n-creating-new-file-in-symlinked-folder.js | 154 +- ...file-is-added-to-the-referenced-project.js | 542 +-- ...ibCheck-and-skipDefaultLibCheck-changes.js | 88 +- ...-file-is-changed-but-its-content-havent.js | 45 +- .../on-sample-project.js | 360 +- ...-different-folders-with-no-files-clause.js | 892 ++--- ...nsitive-references-in-different-folders.js | 832 ++--- .../on-transitive-references.js | 705 ++-- ...n-declarationMap-changes-for-dependency.js | 165 +- ...roject-uses-different-module-resolution.js | 60 +- .../tscWatch/resolutionCache/caching-works.js | 151 +- .../watch-with-configFile.js | 54 +- .../watch-without-configFile.js | 40 +- .../loads-missing-files-from-disk.js | 73 +- .../reusing-type-ref-resolution.js | 450 +-- .../scoped-package-installation.js | 262 +- ...module-goes-missing-and-then-comes-back.js | 120 +- ...are-global-and-installed-at-later-point.js | 117 +- .../with-modules-linked-to-sibling-folder.js | 62 +- ...cluded-file-with-ambient-module-changes.js | 57 +- ...-no-notification-from-fs-for-index-file.js | 333 +- ...le-resolution-changes-to-ambient-module.js | 99 +- ...der-that-already-contains-@types-folder.js | 99 +- ...rogram-with-files-from-external-library.js | 109 +- ...-prefers-declaration-file-over-document.js | 93 +- ...es-field-when-solution-is-already-built.js | 132 +- ...Symlinks-when-solution-is-already-built.js | 134 +- ...n-has-types-field-with-preserveSymlinks.js | 138 +- ...-package-when-solution-is-already-built.js | 132 +- ...Symlinks-when-solution-is-already-built.js | 134 +- ...th-scoped-package-with-preserveSymlinks.js | 138 +- ...son-has-types-field-with-scoped-package.js | 136 +- .../when-packageJson-has-types-field.js | 136 +- ...ubFolder-when-solution-is-already-built.js | 132 +- ...Symlinks-when-solution-is-already-built.js | 134 +- ...le-from-subFolder-with-preserveSymlinks.js | 138 +- ...-package-when-solution-is-already-built.js | 132 +- ...Symlinks-when-solution-is-already-built.js | 134 +- ...th-scoped-package-with-preserveSymlinks.js | 138 +- ...file-from-subFolder-with-scoped-package.js | 136 +- .../when-referencing-file-from-subFolder.js | 136 +- ...-project-when-solution-is-already-built.js | 142 +- .../with-simple-project.js | 154 +- .../extraFileExtensions-are-supported.js | 101 +- ...not-implement-hasInvalidatedResolutions.js | 146 +- ...st-implements-hasInvalidatedResolutions.js | 146 +- ...noEmit-with-composite-with-emit-builder.js | 406 +-- ...it-with-composite-with-semantic-builder.js | 454 +-- ...nError-with-composite-with-emit-builder.js | 254 +- ...or-with-composite-with-semantic-builder.js | 266 +- .../watchApi/semantic-builder-emitOnlyDts.js | 168 +- ...createSemanticDiagnosticsBuilderProgram.js | 49 +- ...n-works-when-returned-without-extension.js | 44 +- ...assed-down-to-the-watch-status-reporter.js | 40 +- ...ting-with-emitOnlyDtsFiles-with-outFile.js | 112 +- .../when-emitting-with-emitOnlyDtsFiles.js | 158 +- ...ing-useSourceOfProjectReferenceRedirect.js | 544 +-- ...-host-implementing-getParsedCommandLine.js | 240 +- ...oject-when-there-is-no-config-file-name.js | 60 +- ...tends-when-there-is-no-config-file-name.js | 80 +- ...-timesouts-on-host-program-gets-updated.js | 86 +- .../fsEvent-for-change-is-repeated.js | 108 +- ...inode-when-rename-event-ends-with-tilde.js | 188 +- ...e-occurs-when-file-is-still-on-the-disk.js | 118 +- ...when-using-file-watching-thats-on-inode.js | 156 +- ...e-occurs-when-file-is-still-on-the-disk.js | 98 +- ...polling-when-renaming-file-in-subfolder.js | 84 +- ...rectory-when-renaming-file-in-subfolder.js | 81 +- ...tchFile-when-renaming-file-in-subfolder.js | 85 +- ...ymlinks-to-folders-in-recursive-folders.js | 55 +- ...hronous-watch-directory-renaming-a-file.js | 198 +- ...ory-with-outDir-and-declaration-enabled.js | 151 +- .../with-non-synchronous-watch-directory.js | 213 +- .../using-dynamic-priority-polling.js | 1195 +++++-- .../using-fixed-chunk-size-polling.js | 91 +- ...eDirectories-option-extendedDiagnostics.js | 64 +- ...-directory-watching-extendedDiagnostics.js | 71 +- ...ption-with-recursive-directory-watching.js | 69 +- .../with-excludeDirectories-option.js | 62 +- ...excludeFiles-option-extendedDiagnostics.js | 64 +- .../watchOptions/with-excludeFiles-option.js | 62 +- .../with-fallbackPolling-option.js | 42 +- .../with-watchDirectory-option.js | 42 +- ...th-watchFile-as-watch-options-to-extend.js | 46 +- .../watchOptions/with-watchFile-option.js | 46 +- .../with-applyChangedToOpenFiles-request.js | 73 +- .../with-updateOpen-request.js | 73 +- ...e-is-in-inferred-project-until-imported.js | 147 +- ...roviderProject-when-host-project-closes.js | 121 +- ...-are-redirects-that-dont-actually-exist.js | 121 +- ...ider-if-there-are-too-many-dependencies.js | 121 +- ...pening-projects-for-find-all-references.js | 300 +- ...s-on-AutoImportProviderProject-creation.js | 136 +- ...covers-from-an-unparseable-package_json.js | 121 +- ...ds-to-automatic-changes-in-node_modules.js | 121 +- ...ponds-to-manual-changes-in-node_modules.js | 151 +- .../Responds-to-package_json-changes.js | 121 +- ...der-when-program-structure-is-unchanged.js | 121 +- ...een-AutoImportProvider-and-main-program.js | 155 +- ...ependencies-are-already-in-main-program.js | 121 +- .../projects-already-inside-node_modules.js | 28 +- .../without-dependencies-listed.js | 121 +- ...-not-remove-scrips-from-InferredProject.js | 29 +- ...-added-later-through-finding-definition.js | 2 +- ...olution-is-reused-from-different-folder.js | 2 +- ...-FLLs-in-Classic-module-resolution-mode.js | 164 +- ...der-FLLs-in-Node-module-resolution-mode.js | 164 +- .../loads-missing-files-from-disk.js | 85 +- ...-when-timeout-occurs-after-installation.js | 244 +- ...n-timeout-occurs-inbetween-installation.js | 242 +- ...-file-with-case-insensitive-file-system.js | 232 +- ...ig-file-with-case-sensitive-file-system.js | 232 +- .../when-calling-goto-definition-of-module.js | 142 +- ...n-creating-new-file-in-symlinked-folder.js | 127 +- ...eive-event-for-the-@types-file-addition.js | 135 +- .../works-using-legacy-resolution-logic.js | 68 +- .../cancellationT/Geterr-is-cancellable.js | 169 +- .../Lower-priority-tasks-are-cancellable.js | 124 +- .../cancellationT/is-attached-to-request.js | 73 +- ...quest-when-projectFile-is-not-specified.js | 148 +- ...stRequest-when-projectFile-is-specified.js | 148 +- ...sesOutFile-should-be-true-if-out-is-set.js | 135 +- ...utFile-should-be-true-if-outFile-is-set.js | 121 +- ...tFile-should-not-be-returned-if-not-set.js | 119 +- ...ojects-all-projects-without-projectPath.js | 236 +- ...figProjects-cascaded-affected-file-list.js | 73 +- .../configProjects-circular-references.js | 119 +- .../configProjects-compileOnSave-disabled.js | 73 +- ...Projects-compileOnSave-in-base-tsconfig.js | 73 +- ...ojects-detect-changes-in-non-root-files.js | 73 +- ...onfigProjects-global-file-shape-changed.js | 73 +- .../configProjects-isolatedModules.js | 75 +- .../configProjects-module-shape-changed.js | 73 +- .../compileOnSave/configProjects-noEmit.js | 75 +- .../configProjects-non-existing-code.js | 119 +- .../compileOnSave/configProjects-outFile.js | 76 +- .../configProjects-removed-code.js | 123 +- ...uptodate-with-changes-in-non-open-files.js | 77 +- ...figProjects-uptodate-with-deleted-files.js | 77 +- .../configProjects-uptodate-with-new-files.js | 89 +- ...cts-uptodate-with-reference-map-changes.js | 73 +- ...ileChange-in-global-file-with-composite.js | 121 +- ...ange-in-global-file-with-decorator-emit.js | 122 +- ...FileChange-in-global-file-with-dts-emit.js | 121 +- .../dtsFileChange-in-global-file.js | 119 +- .../dtsFileChange-in-module-file.js | 119 +- .../emit-in-project-with-dts-emit.js | 76 +- ...it-in-project-with-module-with-dts-emit.js | 75 +- .../emit-in-project-with-module.js | 75 +- .../tsserver/compileOnSave/emit-in-project.js | 76 +- .../compileOnSave/emit-specified-file.js | 73 +- .../emit-with-richRepsonse-as-false.js | 81 +- .../emit-with-richRepsonse-as-true.js | 81 +- .../emit-with-richRepsonse-as-undefined.js | 81 +- .../tsserver/compileOnSave/line-endings.js | 4 +- ...-not-emit-js-files-in-external-projects.js | 42 +- .../use-projectRoot-as-current-directory.js | 42 +- ...ed-from-two-different-drives-of-windows.js | 35 +- .../reference/tsserver/completions/works.js | 119 +- ...-not-count-against-the-resolution-limit.js | 121 +- ...ailable-from-module-specifier-cache-(1).js | 121 +- ...ailable-from-module-specifier-cache-(2).js | 121 +- ...-for-transient-symbols-between-requests.js | 121 +- ...orks-with-PackageJsonAutoImportProvider.js | 121 +- .../tsserver/completionsIncomplete/works.js | 121 +- ...should-stop-at-projectRootPath-if-given.js | 276 ++ ...-searching-for-inferred-project-again-2.js | 140 +- ...en-searching-for-inferred-project-again.js | 140 +- .../tsconfig-for-the-file-does-not-exist.js | 322 +- .../tsconfig-for-the-file-exists.js | 336 +- .../when-projectRootPath-is-not-present.js | 194 +- ...esent-but-file-is-not-from-project-root.js | 195 +- ...should-stop-at-projectRootPath-if-given.js | 82 - ...-are-all-closed-when-the-update-happens.js | 581 +++- ...oject-as-part-of-configured-file-update.js | 762 ++++- ...onfig-file-in-a-folder-with-loose-files.js | 298 +- ...-a-configured-project-without-file-list.js | 122 +- ...has-changed-(new-file-in-list-of-files).js | 240 +- ...ot-files-has-changed-(new-file-on-disk).js | 170 +- ...-when-set-of-root-files-was-not-changed.js | 244 +- ...on-reflected-when-specifying-files-list.js | 163 +- ...e-configured-project-with-the-file-list.js | 99 +- ...te-configured-project-without-file-list.js | 101 +- ...er-old-one-without-file-being-in-config.js | 22 +- ...invoked,-ask-errors-on-it-after-old-one.js | 34 +- ...re-old-one-without-file-being-in-config.js | 22 +- ...nvoked,-ask-errors-on-it-before-old-one.js | 34 +- ...er-old-one-without-file-being-in-config.js | 22 +- ...invoked,-ask-errors-on-it-after-old-one.js | 34 +- ...re-old-one-without-file-being-in-config.js | 22 +- ...nvoked,-ask-errors-on-it-before-old-one.js | 34 +- ...uses-parent-most-node_modules-directory.js | 111 +- ...ached-when-language-service-is-disabled.js | 366 +- ...iles-explicitly-excluded-in-config-file.js | 166 +- .../handle-recreated-files-correctly.js | 190 +- ...ject-if-it-is-referenced-from-root-file.js | 520 ++- ...ndle-@types-if-input-file-list-is-empty.js | 116 +- ...server-when-reading-tsconfig-file-fails.js | 2 +- ...e-tolerated-without-crashing-the-server.js | 96 +- ...ting-files-specified-in-the-config-file.js | 265 +- ...erenced-by-the-project-but-not-its-root.js | 225 +- ...n-if-its-not-the-file-from-same-project.js | 341 +- ...odule-resolution-changes-in-config-file.js | 442 ++- ...nfigured-project-that-has-no-open-files.js | 224 +- ...the-extended-configs-of-closed-projects.js | 763 ++++- ...errors-and-still-try-to-build-a-project.js | 183 +- ...nclude-files-that-start-in-subDirectory.js | 123 +- ...e-extended-configs-of-multiple-projects.js | 882 ++++- ...rk-even-if-language-service-is-disabled.js | 2 +- ...gured-project-does-not-contain-the-file.js | 443 ++- .../when-file-name-starts-with-caret.js | 107 +- ...re-open-detects-correct-default-project.js | 22 +- ...s-not-jump-to-source-if-inlined-sources.js | 2 +- ...indAllReferences-starting-at-definition.js | 325 +- ...findAllReferences-target-does-not-exist.js | 249 +- .../declarationFileMaps/findAllReferences.js | 268 +- ...rencesFull-definition-is-in-mapped-file.js | 254 +- .../findAllReferencesFull.js | 268 +- ...nitionAndBoundSpan-with-file-navigation.js | 442 ++- .../getDefinitionAndBoundSpan.js | 249 +- ...ect-doesnt-include-file-and-its-renamed.js | 244 +- .../getEditsForFileRename.js | 249 +- .../goToDefinition-target-does-not-exist.js | 249 +- .../declarationFileMaps/goToDefinition.js | 249 +- .../declarationFileMaps/goToImplementation.js | 249 +- .../tsserver/declarationFileMaps/goToType.js | 249 +- .../declarationFileMaps/navigateTo.js | 249 +- ...ll-file-is-not-specified-but-project-is.js | 363 +- ...l-neither-file-not-project-is-specified.js | 363 +- .../renameLocations-starting-at-definition.js | 325 +- .../renameLocations-target-does-not-exist.js | 249 +- .../declarationFileMaps/renameLocations.js | 268 +- .../renameLocationsFull.js | 268 +- ...-orphan,-and-orphan-script-info-changes.js | 152 +- ...he-source-file-if-script-info-is-orphan.js | 148 +- ...n-script-info-with-different-scriptKind.js | 2 +- .../works-with-import-fixes.js | 119 +- ...eInferredProjectPerProjectRoot-is-false.js | 174 +- ...h-with-useInferredProjectPerProjectRoot.js | 54 +- ...eference-paths-without-external-project.js | 170 +- .../dynamic-file-without-external-project.js | 31 +- ...Path-is-different-from-currentDirectory.js | 300 +- .../dynamicFiles/opening-untitled-files.js | 398 ++- ...tled-can-convert-positions-to-locations.js | 119 +- .../tsserver/dynamicFiles/untitled.js | 28 +- .../dynamicFiles/walkThroughSnippet.js | 28 +- ...anging-scriptKind-of-the-untitled-files.js | 68 +- ...s-file-is-included-by-module-resolution.js | 2 +- ...n-large-js-file-is-included-by-tsconfig.js | 2 +- ...s-file-is-included-by-module-resolution.js | 2 +- ...n-large-ts-file-is-included-by-tsconfig.js | 2 +- ...e-service-disabled-events-are-triggered.js | 40 +- ...large-file-size-is-determined-correctly.js | 126 +- ...g-file-when-using-default-event-handler.js | 11 +- ...ed-config-file-when-using-event-handler.js | 6 +- ...g-file-when-using-default-event-handler.js | 11 +- ...he-config-file-when-using-event-handler.js | 6 +- ...sabled-when-using-default-event-handler.js | 2 +- ...ct-is-disabled-when-using-event-handler.js | 2 +- ...-false-when-using-default-event-handler.js | 2 +- ...oject-is-false-when-using-event-handler.js | 2 +- ...opened-when-using-default-event-handler.js | 2 +- ...file-is-opened-when-using-event-handler.js | 2 +- ...direct-when-using-default-event-handler.js | 2 +- ...erenceRedirect-when-using-event-handler.js | 2 +- ...roject-when-using-default-event-handler.js | 2 +- ...cation-project-when-using-event-handler.js | 2 +- ...n-file-when-using-default-event-handler.js | 2 +- ...d-by-open-file-when-using-event-handler.js | 2 +- ...he-session-and-project-is-at-root-level.js | 6 +- ...ession-and-project-is-not-at-root-level.js | 15 +- ...tself-if---isolatedModules-is-specified.js | 15 +- ...self-if---out-or---outFile-is-specified.js | 15 +- ...should-be-up-to-date-with-deleted-files.js | 22 +- ...-be-up-to-date-with-newly-created-files.js | 22 +- ...-to-date-with-the-reference-map-changes.js | 27 +- ...session-and-should-contains-only-itself.js | 19 +- ...should-detect-changes-in-non-root-files.js | 19 +- ...nd-should-detect-non-existing-code-file.js | 10 +- ...ion-and-should-detect-removed-code-file.js | 6 +- ...ll-files-if-a-global-file-changed-shape.js | 15 +- ...ould-return-cascaded-affected-file-list.js | 23 +- ...fine-for-files-with-circular-references.js | 6 +- ...et-in-the-session-and-when---out-is-set.js | 10 +- ...n-the-session-and-when---outFile-is-set.js | 10 +- ...in-the-session-and-when-adding-new-file.js | 10 +- ...ssion-and-when-both-options-are-not-set.js | 10 +- ...oundUpdate-and-project-is-at-root-level.js | 8 +- ...Update-and-project-is-not-at-root-level.js | 19 +- ...tself-if---isolatedModules-is-specified.js | 17 +- ...self-if---out-or---outFile-is-specified.js | 17 +- ...should-be-up-to-date-with-deleted-files.js | 24 +- ...-be-up-to-date-with-newly-created-files.js | 24 +- ...-to-date-with-the-reference-map-changes.js | 35 +- ...dUpdate-and-should-contains-only-itself.js | 23 +- ...should-detect-changes-in-non-root-files.js | 23 +- ...nd-should-detect-non-existing-code-file.js | 14 +- ...ate-and-should-detect-removed-code-file.js | 8 +- ...ll-files-if-a-global-file-changed-shape.js | 17 +- ...ould-return-cascaded-affected-file-list.js | 29 +- ...fine-for-files-with-circular-references.js | 8 +- ...nBackgroundUpdate-and-when---out-is-set.js | 14 +- ...kgroundUpdate-and-when---outFile-is-set.js | 14 +- ...ckgroundUpdate-and-when-adding-new-file.js | 14 +- ...pdate-and-when-both-options-are-not-set.js | 14 +- ...oundUpdate-and-project-is-at-root-level.js | 16 +- ...Update-and-project-is-not-at-root-level.js | 35 +- ...tself-if---isolatedModules-is-specified.js | 22 +- ...self-if---out-or---outFile-is-specified.js | 22 +- ...should-be-up-to-date-with-deleted-files.js | 29 +- ...-be-up-to-date-with-newly-created-files.js | 29 +- ...-to-date-with-the-reference-map-changes.js | 59 +- ...dUpdate-and-should-contains-only-itself.js | 36 +- ...should-detect-changes-in-non-root-files.js | 36 +- ...nd-should-detect-non-existing-code-file.js | 27 +- ...ate-and-should-detect-removed-code-file.js | 13 +- ...ll-files-if-a-global-file-changed-shape.js | 22 +- ...ould-return-cascaded-affected-file-list.js | 48 +- ...fine-for-files-with-circular-references.js | 13 +- ...nBackgroundUpdate-and-when---out-is-set.js | 27 +- ...kgroundUpdate-and-when---outFile-is-set.js | 27 +- ...ckgroundUpdate-and-when-adding-new-file.js | 27 +- ...pdate-and-when-both-options-are-not-set.js | 27 +- .../canUseWatchEvents-without-canUseEvents.js | 10 +- .../events/watchEvents/canUseWatchEvents.js | 10 +- .../caches-auto-imports-in-the-same-file.js | 119 +- ...ckage.json-is-changed-inconsequentially.js | 119 +- ...ansient-symbols-through-program-updates.js | 119 +- ...-file-is-opened-with-different-contents.js | 2 +- ...idates-the-cache-when-files-are-deleted.js | 178 +- ...ates-the-cache-when-new-files-are-added.js | 136 +- ...ge-results-in-AutoImportProvider-change.js | 119 +- ...ject-when-set-of-root-files-has-changed.js | 107 +- ...zyConfiguredProjectsFromExternalProject.js | 96 +- ...config-file-name-with-difference-casing.js | 79 +- ...-when-set-of-root-files-was-not-changed.js | 120 +- ...s-changes-in-lib-section-of-config-file.js | 242 +- ...zyConfiguredProjectsFromExternalProject.js | 347 +- ...tly-handling-add-or-remove-tsconfig---1.js | 306 +- ...zyConfiguredProjectsFromExternalProject.js | 778 ++++- ...tly-handling-add-or-remove-tsconfig---2.js | 530 ++- ...zyConfiguredProjectsFromExternalProject.js | 348 +- ...-opened-from-the-external-project-works.js | 438 ++- ...t-crash-if-external-file-does-not-exist.js | 39 +- .../external-project-for-dynamic-file.js | 89 +- ...rnal-project-that-included-config-files.js | 418 ++- ...fter-configured-project-and-then-closed.js | 201 +- ...ig-file-opened-after-configured-project.js | 236 +- ...re-jsconfig-creation-watcher-is-invoked.js | 547 ++- ...ProjectsFromExternalProject-is-disabled.js | 314 +- ...d-state-is-updated-in-external-projects.js | 311 +- .../externalProjects/load-global-plugins.js | 39 +- .../remove-not-listed-external-projects.js | 113 +- ...non-existing-directories-in-config-file.js | 216 +- ...ose-external-project-with-no-open-files.js | 159 +- .../when-file-name-starts-with-caret.js | 83 +- ...-was-updated-and-no-longer-has-the-file.js | 272 +- ...nging-module-name-with-different-casing.js | 22 +- ...ied-with-a-case-insensitive-file-system.js | 80 +- ...hen-renaming-file-with-different-casing.js | 32 +- ...ied-with-a-case-insensitive-file-system.js | 2 +- .../works-when-taking-position.js | 2 +- ...rks-with-file-moved-to-inferred-project.js | 222 +- .../works-with-multiple-projects.js | 236 +- .../array-destructuring-declaration.js | 119 +- .../const-variable-declaration.js | 119 +- .../nested-object-declaration.js | 119 +- ...nces-that-renames-destructured-property.js | 119 +- .../object-destructuring-declaration.js | 119 +- .../should-get-file-references.js | 119 +- ...ould-skip-lineText-from-file-references.js | 119 +- .../skips-lib.d.ts-files.js | 119 +- ...ggests-only-.js-file-for-a-.js-filepath.js | 134 +- ...ggests-only-.ts-file-for-a-.ts-filepath.js | 124 +- ...excluding-node_modules-within-a-project.js | 124 +- .../does-not-issue-errors-on-jsdoc-in-TS.js | 136 +- .../does-not-issue-errors-on-jsdoc-in-TS2.js | 136 +- .../should-not-crash-in-tsserver.js | 41 +- .../should-not-error.js | 12 +- .../should-not-error.js | 12 +- ...n-files-should-not-schedule-any-refresh.js | 7 +- .../create-inferred-project.js | 36 +- .../disable-inferred-project.js | 17 +- ...oject-root-with-case-insensitive-system.js | 397 ++- ...project-root-with-case-sensitive-system.js | 397 ++- .../inferred-projects-per-project-root.js | 104 +- .../project-settings-for-inferred-projects.js | 199 +- ...or-inferred-projects-when-set-undefined.js | 148 +- ...-project-created-while-opening-the-file.js | 172 +- ...should-support-files-without-extensions.js | 27 +- ...project-if-useOneInferredProject-is-set.js | 296 +- ...ting-inferred-project-has-no-root-files.js | 52 +- ...Open-request-does-not-corrupt-documents.js | 73 +- ...-string-for-a-working-link-in-a-comment.js | 136 +- ...y-parts-for-a-working-link-in-a-comment.js | 136 +- ...-string-for-a-working-link-in-a-comment.js | 136 +- ...y-parts-for-a-working-link-in-a-comment.js | 136 +- ...-string-for-a-working-link-in-a-comment.js | 136 +- ...de-a-string-for-a-working-link-in-a-tag.js | 136 +- ...y-parts-for-a-working-link-in-a-comment.js | 136 +- ...plus-a-span-for-a-working-link-in-a-tag.js | 136 +- ...-string-for-a-working-link-in-a-comment.js | 136 +- ...de-a-string-for-a-working-link-in-a-tag.js | 136 +- ...-a-span-for-a-working-link-in-a-comment.js | 136 +- ...plus-a-span-for-a-working-link-in-a-tag.js | 136 +- ...-string-for-a-working-link-in-a-comment.js | 136 +- ...y-parts-for-a-working-link-in-a-comment.js | 136 +- ...-string-for-a-working-link-in-a-comment.js | 136 +- ...y-parts-for-a-working-link-in-a-comment.js | 136 +- ...ame-file-under-differing-paths-settings.js | 300 +- ...orrectly-on-case-sensitive-file-systems.js | 22 +- .../with-config-with-redirection.js | 284 +- .../tsserver/libraryResolution/with-config.js | 291 +- ...when-referencing-file-from-another-file.js | 33 +- ...t-to-2-if-the-project-has-js-root-files.js | 37 +- ...-js-root-files-are-removed-from-project.js | 2 +- ...metadata-when-the-command-returns-array.js | 123 +- ...etadata-when-the-command-returns-object.js | 123 +- .../returns-undefined-correctly.js | 123 +- .../tsserver/moduleResolution/node10Result.js | 2667 ++++++++++----- ...en-package-json-with-type-module-exists.js | 156 +- .../package-json-file-is-edited.js | 156 +- .../using-referenced-project-built.js | 32 +- .../using-referenced-project.js | 32 +- .../caches-importability-within-a-file.js | 119 +- .../caches-module-specifiers-within-a-file.js | 119 +- ...date-the-cache-when-new-files-are-added.js | 137 +- ...n-in-contained-node_modules-directories.js | 119 +- ...he-cache-when-local-packageJson-changes.js | 119 +- ...-when-module-resolution-settings-change.js | 213 +- ...ache-when-symlinks-are-added-or-removed.js | 137 +- ...-the-cache-when-user-preferences-change.js | 119 +- ...ate-symbols-when-searching-all-projects.js | 254 +- .../navTo/should-de-duplicate-symbols.js | 240 +- .../navTo/should-not-include-type-symbols.js | 107 +- .../navTo/should-work-with-Deprecated.js | 107 +- ...ould-be-marked-if-only-on-string-values.js | 2 +- .../openfile/can-open-same-file-again.js | 126 +- .../different-content-refreshes-sourceFile.js | 89 +- .../openfile/does-not-refresh-sourceFile.js | 89 +- ...ot-refresh-sourceFile-if-contents-match.js | 89 +- ...ile-and-then-close-refreshes-sourceFile.js | 89 +- ...ot-is-used-with-case-insensitive-system.js | 534 ++- ...root-is-used-with-case-sensitive-system.js | 629 +++- .../openfile/realoaded-with-empty-content.js | 99 +- ...ject-even-if-project-refresh-is-pending.js | 131 +- ...-directives,-they-are-handled-correcrly.js | 32 +- ...re-added,-caches-them,-and-watches-them.js | 109 +- ...ultiple-package.json-files-when-present.js | 115 +- ...r-deletion,-and-removes-them-from-cache.js | 133 +- .../handles-empty-package.json.js | 133 +- ...-errors-in-json-parsing-of-package.json.js | 133 +- .../files-are-added-to-inferred-project.js | 2 +- ...ternal-module-name-resolution-is-reused.js | 2 +- ...oImportProvider-or-handle-package-jsons.js | 2 +- ...-include-auto-type-reference-directives.js | 2 +- ...de-referenced-files-from-unopened-files.js | 2 +- ...t-go-to-definition-on-module-specifiers.js | 2 +- ...-diagnostics-are-returned-with-no-error.js | 58 +- .../throws-unsupported-commands.js | 2 +- .../tsserver/plugins/With-global-plugins.js | 73 +- .../tsserver/plugins/With-local-plugins.js | 81 +- ...ith-session-and-custom-protocol-message.js | 77 +- .../getSupportedCodeFixes-can-be-proxied.js | 77 +- ...-external-files-with-config-file-reload.js | 135 +- ...on-ts-extensions-with-wildcard-matching.js | 107 +- .../pluginsAsync/adds-external-files.js | 20 +- .../plugins-are-not-loaded-immediately.js | 4 +- ...er-even-if-imports-resolve-out-of-order.js | 4 +- ...ect-is-closed-before-plugins-are-loaded.js | 4 +- ...sends-projectsUpdatedInBackground-event.js | 4 +- ...-generated-when-the-config-file-changes.js | 39 +- ...when-the-config-file-doesnt-have-errors.js | 2 +- ...nerated-when-the-config-file-has-errors.js | 2 +- ...-file-opened-and-config-file-has-errors.js | 2 +- ...le-opened-and-doesnt-contain-any-errors.js | 2 +- ...rs-but-suppressDiagnosticEvents-is-true.js | 2 +- ...s-contains-the-project-reference-errors.js | 2 +- ...ts---diagnostics-for-corrupted-config-1.js | 213 +- ...ts---diagnostics-for-corrupted-config-2.js | 213 +- ...rojects---diagnostics-for-missing-files.js | 101 +- ...-same-ambient-module-and-is-also-module.js | 22 +- ...iagnostics-after-noUnusedLabels-changes.js | 213 +- .../document-is-not-contained-in-project.js | 179 +- ...project---diagnostics-for-missing-files.js | 46 +- ...project-structure-and-reports-no-errors.js | 50 +- .../projectErrors/for-external-project.js | 64 +- .../projectErrors/for-inferred-project.js | 35 +- .../getting-errors-before-opening-file.js | 5 +- ...-when-timeout-occurs-after-installation.js | 116 +- ...n-timeout-occurs-inbetween-installation.js | 106 +- ...pened-right-after-closing-the-root-file.js | 144 +- ...hen-json-is-root-file-found-by-tsconfig.js | 12 +- ...json-is-not-root-file-found-by-tsconfig.js | 12 +- ...esnt-exist-on-disk-yet-with-projectRoot.js | 12 +- ...t-exist-on-disk-yet-without-projectRoot.js | 12 +- .../projectErrors/when-options-change.js | 196 +- ...-global-error-gerErr-with-sync-commands.js | 73 +- ...or-returns-includes-global-error-getErr.js | 12 +- ...-includes-global-error-geterrForProject.js | 12 +- ...-as-project-build-with-external-project.js | 77 +- ...-on-dependency-and-change-to-dependency.js | 150 +- .../save-on-dependency-and-change-to-usage.js | 150 +- ...pendency-and-local-change-to-dependency.js | 150 +- ...on-dependency-and-local-change-to-usage.js | 150 +- ...y-with-project-and-change-to-dependency.js | 150 +- ...ndency-with-project-and-change-to-usage.js | 150 +- ...-project-and-local-change-to-dependency.js | 150 +- ...-with-project-and-local-change-to-usage.js | 150 +- .../save-on-dependency-with-project.js | 150 +- ...-usage-project-and-change-to-dependency.js | 147 +- ...-with-usage-project-and-change-to-usage.js | 147 +- ...-project-and-local-change-to-dependency.js | 147 +- ...usage-project-and-local-change-to-usage.js | 147 +- .../save-on-dependency-with-usage-project.js | 147 +- .../save-on-dependency.js | 150 +- .../save-on-usage-and-change-to-dependency.js | 147 +- .../save-on-usage-and-change-to-usage.js | 147 +- ...nd-local-change-to-dependency-with-file.js | 147 +- ...on-usage-and-local-change-to-dependency.js | 147 +- ...-and-local-change-to-usage-with-project.js | 147 +- ...save-on-usage-and-local-change-to-usage.js | 147 +- ...e-with-project-and-change-to-dependency.js | 147 +- ...-usage-with-project-and-change-to-usage.js | 147 +- .../save-on-usage-with-project.js | 147 +- .../save-on-usage.js | 147 +- ...-on-dependency-and-change-to-dependency.js | 77 +- ...-save-on-dependency-and-change-to-usage.js | 73 +- ...pendency-and-local-change-to-dependency.js | 77 +- ...on-dependency-and-local-change-to-usage.js | 73 +- ...y-with-project-and-change-to-dependency.js | 77 +- ...ndency-with-project-and-change-to-usage.js | 73 +- ...-project-and-local-change-to-dependency.js | 77 +- ...-with-project-and-local-change-to-usage.js | 73 +- ...pen-and-save-on-dependency-with-project.js | 73 +- ...ject-is-not-open-and-save-on-dependency.js | 73 +- ...save-on-usage-and-change-to-depenedency.js | 77 +- ...n-and-save-on-usage-and-change-to-usage.js | 73 +- ...on-usage-and-local-change-to-dependency.js | 77 +- ...save-on-usage-and-local-change-to-usage.js | 73 +- ...-with-project-and-change-to-depenedency.js | 77 +- ...-usage-with-project-and-change-to-usage.js | 73 +- ...-project-and-local-change-to-dependency.js | 77 +- ...-with-project-and-local-change-to-usage.js | 73 +- ...not-open-and-save-on-usage-with-project.js | 73 +- ...y-project-is-not-open-and-save-on-usage.js | 73 +- ...t-is-not-open-gerErr-with-sync-commands.js | 75 +- ...n-dependency-project-is-not-open-getErr.js | 12 +- ...cy-project-is-not-open-geterrForProject.js | 42 +- ...-file-is-open-gerErr-with-sync-commands.js | 149 +- ...-when-the-depedency-file-is-open-getErr.js | 22 +- ...depedency-file-is-open-geterrForProject.js | 32 +- ...t-is-not-open-gerErr-with-sync-commands.js | 76 +- ...n-dependency-project-is-not-open-getErr.js | 12 +- ...cy-project-is-not-open-geterrForProject.js | 42 +- ...-file-is-open-gerErr-with-sync-commands.js | 150 +- ...-when-the-depedency-file-is-open-getErr.js | 22 +- ...depedency-file-is-open-geterrForProject.js | 32 +- .../ancestor-and-project-ref-management.js | 291 +- ...disableSourceOfProjectReferenceRedirect.js | 77 +- ...port-with-referenced-project-when-built.js | 76 +- .../auto-import-with-referenced-project.js | 76 +- ...ssfully-find-references-with-out-option.js | 291 +- ...indirect-project-but-not-in-another-one.js | 2 +- ...dProjectLoad-is-set-in-indirect-project.js | 2 +- ...-if-disableReferencedProjectLoad-is-set.js | 2 +- ...oes-not-error-on-container-only-project.js | 254 +- ...-are-disabled-and-a-decl-map-is-missing.js | 244 +- ...-are-disabled-and-a-decl-map-is-present.js | 244 +- ...s-are-enabled-and-a-decl-map-is-missing.js | 244 +- ...s-are-enabled-and-a-decl-map-is-present.js | 244 +- ...-are-disabled-and-a-decl-map-is-missing.js | 244 +- ...-are-disabled-and-a-decl-map-is-present.js | 244 +- ...s-are-enabled-and-a-decl-map-is-missing.js | 244 +- ...s-are-enabled-and-a-decl-map-is-present.js | 244 +- ...-are-disabled-and-a-decl-map-is-missing.js | 123 +- ...-are-disabled-and-a-decl-map-is-present.js | 123 +- ...s-are-enabled-and-a-decl-map-is-missing.js | 123 +- ...s-are-enabled-and-a-decl-map-is-present.js | 123 +- ...-are-disabled-and-a-decl-map-is-missing.js | 123 +- ...-are-disabled-and-a-decl-map-is-present.js | 187 +- ...s-are-enabled-and-a-decl-map-is-missing.js | 187 +- ...s-are-enabled-and-a-decl-map-is-present.js | 187 +- .../sibling-projects.js | 209 +- ...ding-references-in-overlapping-projects.js | 333 +- ...solution-is-built-with-preserveSymlinks.js | 22 +- ...-and-has-index.ts-and-solution-is-built.js | 22 +- ...tion-is-not-built-with-preserveSymlinks.js | 22 +- ...-has-index.ts-and-solution-is-not-built.js | 22 +- ...solution-is-built-with-preserveSymlinks.js | 22 +- ...th-scoped-package-and-solution-is-built.js | 22 +- ...tion-is-not-built-with-preserveSymlinks.js | 22 +- ...coped-package-and-solution-is-not-built.js | 22 +- ...solution-is-built-with-preserveSymlinks.js | 22 +- ...le-from-subFolder-and-solution-is-built.js | 22 +- ...tion-is-not-built-with-preserveSymlinks.js | 22 +- ...rom-subFolder-and-solution-is-not-built.js | 22 +- ...solution-is-built-with-preserveSymlinks.js | 22 +- ...th-scoped-package-and-solution-is-built.js | 22 +- ...tion-is-not-built-with-preserveSymlinks.js | 22 +- ...coped-package-and-solution-is-not-built.js | 22 +- ...disableSourceOfProjectReferenceRedirect.js | 141 +- ...ect-when-referenced-project-is-not-open.js | 92 +- ...disableSourceOfProjectReferenceRedirect.js | 220 +- ...project-when-referenced-project-is-open.js | 168 +- ...ject-is-directly-referenced-by-solution.js | 14 +- ...ct-is-indirectly-referenced-by-solution.js | 14 +- ...om-composite-and-non-composite-projects.js | 250 +- ...nced-project-and-using-declaration-maps.js | 158 +- ...ot-file-is-file-from-referenced-project.js | 158 +- ...indirect-project-but-not-in-another-one.js | 2 +- ...dProjectLoad-is-set-in-indirect-project.js | 2 +- ...-if-disableReferencedProjectLoad-is-set.js | 2 +- ...ces-open-file-through-project-reference.js | 14 +- ...ct-is-indirectly-referenced-by-solution.js | 14 +- ...nction-as-object-literal-property-types.js | 276 +- ...row-function-as-object-literal-property.js | 141 +- ...ss-when-using-arrow-function-assignment.js | 276 +- ...s-when-using-method-of-class-expression.js | 276 +- ...ness-when-using-object-literal-property.js | 276 +- ...cts-are-open-and-one-project-references.js | 522 ++- ...ts-have-allowJs-and-emitDeclarationOnly.js | 12 +- ...ng-solution-and-siblings-are-not-loaded.js | 77 +- ...dts-changes-with-timeout-before-request.js | 165 +- .../dependency-dts-changes.js | 152 +- .../dependency-dts-created.js | 152 +- .../dependency-dts-deleted.js | 152 +- .../dependency-dts-not-present.js | 148 +- ...Map-changes-with-timeout-before-request.js | 165 +- .../dependency-dtsMap-changes.js | 152 +- .../dependency-dtsMap-created.js | 152 +- .../dependency-dtsMap-deleted.js | 152 +- .../dependency-dtsMap-not-present.js | 148 +- .../configHasNoReference/rename-locations.js | 148 +- ...ile-changes-with-timeout-before-request.js | 148 +- .../usage-file-changes.js | 148 +- ...dts-changes-with-timeout-before-request.js | 165 +- .../dependency-dts-changes.js | 152 +- .../dependency-dts-created.js | 152 +- .../dependency-dts-deleted.js | 152 +- .../dependency-dts-not-present.js | 148 +- ...Map-changes-with-timeout-before-request.js | 165 +- .../dependency-dtsMap-changes.js | 152 +- .../dependency-dtsMap-created.js | 152 +- .../dependency-dtsMap-deleted.js | 152 +- .../dependency-dtsMap-not-present.js | 148 +- ...rce-changes-with-timeout-before-request.js | 148 +- .../dependency-source-changes.js | 148 +- .../configWithReference/rename-locations.js | 148 +- ...ile-changes-with-timeout-before-request.js | 148 +- .../configWithReference/usage-file-changes.js | 148 +- .../when-projects-are-not-built.js | 148 +- ...dts-changes-with-timeout-before-request.js | 165 +- .../dependency-dts-changes.js | 152 +- .../dependency-dts-created.js | 152 +- .../dependency-dts-deleted.js | 152 +- .../dependency-dts-not-present.js | 148 +- ...Map-changes-with-timeout-before-request.js | 165 +- .../dependency-dtsMap-changes.js | 152 +- .../dependency-dtsMap-created.js | 152 +- .../dependency-dtsMap-deleted.js | 152 +- .../dependency-dtsMap-not-present.js | 148 +- .../disabledSourceRef/rename-locations.js | 148 +- ...ile-changes-with-timeout-before-request.js | 148 +- .../disabledSourceRef/usage-file-changes.js | 148 +- ...dts-changes-with-timeout-before-request.js | 241 +- .../dependency-dts-changes.js | 227 +- .../dependency-dts-created.js | 233 +- .../dependency-dts-deleted.js | 234 +- .../dependency-dts-not-present.js | 222 +- ...Map-changes-with-timeout-before-request.js | 241 +- .../dependency-dtsMap-changes.js | 227 +- .../dependency-dtsMap-created.js | 227 +- .../dependency-dtsMap-deleted.js | 227 +- .../dependency-dtsMap-not-present.js | 222 +- .../goToDef-and-rename-locations.js | 222 +- ...ile-changes-with-timeout-before-request.js | 222 +- .../usage-file-changes.js | 222 +- ...dts-changes-with-timeout-before-request.js | 241 +- .../dependency-dts-changes.js | 227 +- .../dependency-dts-created.js | 232 +- .../dependency-dts-deleted.js | 234 +- .../dependency-dts-not-present.js | 222 +- ...Map-changes-with-timeout-before-request.js | 241 +- .../dependency-dtsMap-changes.js | 227 +- .../dependency-dtsMap-created.js | 227 +- .../dependency-dtsMap-deleted.js | 227 +- .../dependency-dtsMap-not-present.js | 222 +- ...rce-changes-with-timeout-before-request.js | 222 +- .../dependency-source-changes.js | 222 +- .../gotoDef-and-rename-locations.js | 222 +- ...ile-changes-with-timeout-before-request.js | 222 +- .../configWithReference/usage-file-changes.js | 222 +- .../when-projects-are-not-built.js | 222 +- ...dts-changes-with-timeout-before-request.js | 242 +- .../dependency-dts-changes.js | 228 +- .../dependency-dts-created.js | 234 +- .../dependency-dts-deleted.js | 235 +- .../dependency-dts-not-present.js | 223 +- ...Map-changes-with-timeout-before-request.js | 242 +- .../dependency-dtsMap-changes.js | 228 +- .../dependency-dtsMap-created.js | 228 +- .../dependency-dtsMap-deleted.js | 228 +- .../dependency-dtsMap-not-present.js | 223 +- .../gotoDef-and-rename-locations.js | 223 +- ...ile-changes-with-timeout-before-request.js | 223 +- .../disabledSourceRef/usage-file-changes.js | 223 +- .../can-go-to-definition-correctly.js | 147 +- ...dts-changes-with-timeout-before-request.js | 164 +- .../dependency-dts-changes.js | 151 +- .../dependency-dts-created.js | 154 +- .../dependency-dts-deleted.js | 157 +- .../dependency-dts-not-present.js | 147 +- ...Map-changes-with-timeout-before-request.js | 164 +- .../dependency-dtsMap-changes.js | 151 +- .../dependency-dtsMap-created.js | 151 +- .../dependency-dtsMap-deleted.js | 151 +- .../dependency-dtsMap-not-present.js | 147 +- ...ile-changes-with-timeout-before-request.js | 147 +- .../usage-file-changes.js | 147 +- .../can-go-to-definition-correctly.js | 147 +- ...dts-changes-with-timeout-before-request.js | 147 +- .../dependency-dts-changes.js | 147 +- .../dependency-dts-created.js | 153 +- .../dependency-dts-deleted.js | 153 +- .../dependency-dts-not-present.js | 147 +- ...Map-changes-with-timeout-before-request.js | 147 +- .../dependency-dtsMap-changes.js | 147 +- .../dependency-dtsMap-created.js | 147 +- .../dependency-dtsMap-deleted.js | 147 +- .../dependency-dtsMap-not-present.js | 147 +- ...rce-changes-with-timeout-before-request.js | 164 +- .../dependency-source-changes.js | 151 +- ...ile-changes-with-timeout-before-request.js | 147 +- .../configWithReference/usage-file-changes.js | 147 +- .../when-projects-are-not-built.js | 147 +- .../can-go-to-definition-correctly.js | 148 +- ...dts-changes-with-timeout-before-request.js | 165 +- .../dependency-dts-changes.js | 152 +- .../dependency-dts-created.js | 155 +- .../dependency-dts-deleted.js | 158 +- .../dependency-dts-not-present.js | 148 +- ...Map-changes-with-timeout-before-request.js | 165 +- .../dependency-dtsMap-changes.js | 152 +- .../dependency-dtsMap-created.js | 152 +- .../dependency-dtsMap-deleted.js | 152 +- .../dependency-dtsMap-not-present.js | 148 +- ...ile-changes-with-timeout-before-request.js | 148 +- .../disabledSourceRef/usage-file-changes.js | 148 +- ...projects-at-opened-and-closed-correctly.js | 415 ++- ...-are-handled-correctly-on-watch-trigger.js | 160 +- .../Properly-handle-Windows-style-outDir.js | 149 +- .../projects/assert-when-removing-project.js | 76 +- ...iles-are-reflected-in-project-structure.js | 191 +- .../clear-mixed-content-file-after-closing.js | 39 +- .../projects/config-file-is-deleted.js | 209 +- ...orrectly-migrate-files-between-projects.js | 270 +- ...zyConfiguredProjectsFromExternalProject.js | 119 +- .../deferred-files-in-the-project-context.js | 62 +- .../deleted-files-affect-project-structure.js | 79 +- ...folders-for-default-configured-projects.js | 346 +- .../external-project-including-config-file.js | 63 +- ...configured-project-that-will-be-removed.js | 133 +- ...ith-typeAcquisition-when-safe-type-list.js | 221 +- ...-and-closed-affecting-multiple-projects.js | 73 +- ...ith-mixed-content-are-handled-correctly.js | 255 +- ...getting-project-from-orphan-script-info.js | 76 +- ...directory-watch-invoke-on-file-creation.js | 64 +- ...issing-files-added-with-tripleslash-ref.js | 18 +- ...les-excluded-by-a-custom-safe-type-list.js | 220 +- ...les-excluded-by-a-legacy-safe-type-list.js | 217 +- ...files-excluded-by-the-default-type-list.js | 265 +- ...configured-project-that-will-be-removed.js | 29 +- .../loading-files-with-correct-priority.js | 664 +++- ...luded-in-config-file-(applyCodeChanges).js | 24 +- ...ncluded-in-config-file-(openClientFile).js | 19 +- ...irectory-watch-invoke-on-open-file-save.js | 147 +- ...tsconfig-script-block-diagnostic-errors.js | 373 ++- ...erred-if-files-are-not-added-or-removed.js | 81 +- ...configured-project-that-will-be-removed.js | 133 +- ...st-for-crash-in-acquireOrUpdateDocument.js | 292 +- .../reload-regular-file-after-closing.js | 39 +- ...Reload-but-has-svc-for-previous-version.js | 77 +- ...iles-excluded-from-a-configured-project.js | 268 +- ...e-features-when-the-files-are-too-large.js | 201 +- ...roject-with-a-disabled-language-service.js | 61 +- ...-from-different-caches-are-incompatible.js | 39 +- ...t-provides-redirect-info-when-requested.js | 150 +- ...updates-to-redirect-info-when-requested.js | 293 +- ...resolved-and-redirect-info-is-requested.js | 79 +- ...e-configuration-file-cannot-be-resolved.js | 79 +- ...che-handles-changes-in-project-settings.js | 212 +- .../projects/tsconfig-script-block-support.js | 246 +- .../projectsWithReferences/sample-project.js | 173 +- ...es-with-deleting-referenced-config-file.js | 155 +- ...ing-transitively-referenced-config-file.js | 155 +- ...ces-with-edit-in-referenced-config-file.js | 190 +- ...ive-references-with-edit-on-config-file.js | 250 +- ...ansitive-references-with-non-local-edit.js | 129 +- ...es-with-deleting-referenced-config-file.js | 161 +- ...ing-transitively-referenced-config-file.js | 161 +- ...les-with-edit-in-referenced-config-file.js | 188 +- ...-without-files-with-edit-on-config-file.js | 246 +- ...ences-without-files-with-non-local-edit.js | 129 +- ...ndles-canonicalization-of-tsconfig-path.js | 119 +- ...es-moving-statement-to-an-existing-file.js | 119 +- ...-that-is-not-included-in-the-TS-project.js | 119 +- ...dles-moving-statements-to-a-non-TS-file.js | 119 +- .../handles-text-changes-in-tsconfig.js | 119 +- .../refactors/use-formatting-options.js | 2 +- ...cript-info-doesnt-have-any-project-open.js | 2 +- .../reload/should-work-with-temp-file.js | 2 +- .../reloadProjects/configured-project.js | 178 +- .../external-project-with-config-file.js | 167 +- .../reloadProjects/external-project.js | 54 +- .../reloadProjects/inferred-project.js | 17 +- ...prefixText-and-suffixText-when-disabled.js | 2 +- ...r-is-based-on-file-of-rename-initiation.js | 2 +- .../rename/works-with-fileToRename.js | 2 +- ...-prefixText-and-suffixText-when-enabled.js | 2 +- ...unnecessary-lookup-invalidation-on-save.js | 110 +- ...an-load-typings-that-are-proper-modules.js | 73 +- .../disable-suggestion-diagnostics.js | 37 +- ...le-name-from-files-in-different-folders.js | 157 +- ...e-module-name-from-files-in-same-folder.js | 129 +- ...ative-module-name-from-inferred-project.js | 104 +- .../not-sharing-across-references.js | 123 +- .../npm-install-@types-works.js | 57 +- ...le-name-from-files-in-different-folders.js | 157 +- ...e-module-name-from-files-in-same-folder.js | 129 +- ...tore-the-states-for-configured-projects.js | 148 +- ...estore-the-states-for-inferred-projects.js | 39 +- .../sharing-across-references.js | 122 +- ...ld-property-handle-missing-config-files.js | 99 +- ...hould-remove-the-module-not-found-error.js | 11 +- .../resolutionCache/suggestion-diagnostics.js | 39 +- .../suppressed-diagnostic-events.js | 11 +- ...-from-config-file-path-if-config-exists.js | 153 +- ...exists-but-does-not-specifies-typeRoots.js | 175 +- .../resolutionCache/when-resolution-fails.js | 82 +- .../when-resolves-to-ambient-module.js | 82 +- ...wild-card-directories-in-config-project.js | 92 +- .../closed-script-infos.js | 21 +- ...ectly-when-typings-are-added-or-removed.js | 205 +- ...rnal-project-with-skipLibCheck-as-false.js | 65 +- .../skipLibCheck/jsonly-external-project.js | 62 +- .../skipLibCheck/jsonly-inferred-project.js | 78 +- ...r-in-configured-js-project-with-tscheck.js | 159 +- ...rror-in-configured-project-with-tscheck.js | 157 +- .../reports-semantic-error-with-tscheck.js | 29 +- ...eclaration-files-with-skipLibCheck=true.js | 157 +- .../works-for-simple-JavaScript.js | 29 +- ...tion-when-project-compiles-from-sources.js | 64 +- ...s-in-typings-folder-and-then-recompiles.js | 22 +- ...mpiles-after-deleting-generated-folders.js | 66 +- ...ping-when-project-compiles-from-sources.js | 64 +- ...s-in-typings-folder-and-then-recompiles.js | 38 +- ...mpiles-after-deleting-generated-folders.js | 86 +- ...name-in-common-file-renames-all-project.js | 148 +- .../when-not-symlink-but-differs-in-casing.js | 2 +- ...ences-resolution-after-program-creation.js | 152 +- ...ed-project-and-semantic-operations-fail.js | 2 +- ...-include-auto-type-reference-directives.js | 2 +- ...de-referenced-files-from-unopened-files.js | 2 +- .../throws-on-unsupported-commands.js | 2 +- ...emoved-and-added-with-different-content.js | 121 +- .../telemetry/counts-files-by-extension.js | 2 +- ...s-whether-language-service-was-disabled.js | 2 +- .../telemetry/does-not-expose-paths.js | 2 +- .../does-nothing-for-inferred-project.js | 29 +- ...ven-for-project-with-ts-check-in-config.js | 35 +- .../tsserver/telemetry/not-for-ts-file.js | 2 +- .../telemetry/only-sends-an-event-once.js | 2 +- .../sends-event-for-inferred-project.js | 54 +- ...es,-include,-exclude,-and-compileOnSave.js | 2 +- .../sends-telemetry-for-file-sizes.js | 34 +- ...-telemetry-for-typeAcquisition-settings.js | 37 +- .../telemetry/works-with-external-project.js | 2 +- ...-JS-file-is-too-large-to-load-into-text.js | 172 +- .../tsserver/resolves-the-symlink-path.js | 2 +- .../does-not-depend-on-extension.js | 194 +- .../prefer-typings-in-second-pass.js | 185 +- ...portDefault-exportDefault-importDefault.js | 2 +- ...OnlyNamedImport-namedExport-namedImport.js | 2 +- ...lyExportFrom-exportStarFrom-namedImport.js | 2 +- ...OnlyNamedImport-namedExport-namedImport.js | 2 +- ...spaceImport-exportDefault-importDefault.js | 2 +- ...mespaceImport-exportEquals-importEquals.js | 2 +- ...NamespaceImport-namedExport-namedImport.js | 2 +- ...ortFrom-exportNamespaceFrom-namedImport.js | 2 +- ...enceDirective-contains-UpperCasePackage.js | 95 +- ...s-relative-path-and-in-a-sibling-folder.js | 77 +- ...ted-if-program-structure-did-not-change.js | 35 +- ...projects-discover-from-bower_components.js | 271 +- .../typingsInstaller/configured-projects.js | 279 +- .../typingsInstaller/discover-from-bower.js | 271 +- ...rom-node_modules-empty-types-has-import.js | 269 +- .../discover-from-node_modules-empty-types.js | 196 +- ...scover-from-node_modules-explicit-types.js | 223 +- .../discover-from-node_modules.js | 271 +- .../typingsInstaller/expired-cache-entry.js | 134 +- .../external-projects-autoDiscovery.js | 143 +- .../external-projects-duplicate-package.js | 111 +- .../external-projects-no-auto-typings.js | 75 +- ...s-no-type-acquisition-with-enable-false.js | 79 +- ...ts-no-type-acquisition-with-js-ts-files.js | 82 +- .../external-projects-no-type-acquisition.js | 185 +- ...ith-disableFilenameBasedTypeAcquisition.js | 118 +- .../external-projects-type-acquisition.js | 218 +- .../typingsInstaller/external-projects.js | 70 +- ...ith-disableFilenameBasedTypeAcquisition.js | 70 +- .../typingsInstaller/inferred-projects.js | 134 +- .../install-typings-for-unresolved-imports.js | 137 +- ...date-the-resolutions-with-trimmed-names.js | 239 +- .../invalidate-the-resolutions.js | 239 +- .../local-module-should-not-be-picked-up.js | 205 +- .../typingsInstaller/malformed-packagejson.js | 182 +- .../typingsInstaller/multiple-projects.js | 287 +- .../non-expired-cache-entry.js | 71 +- ...mes-from-nonrelative-unresolved-imports.js | 100 +- .../progress-notification-for-error.js | 65 +- .../typingsInstaller/progress-notification.js | 134 +- ...otPath-is-provided-for-inferred-project.js | 91 +- ...utions-pointing-to-js-on-typing-install.js | 129 +- .../typingsInstaller/scoped-name-discovery.js | 264 +- .../should-handle-node-core-modules.js | 263 +- ...d-not-initialize-invaalid-package-names.js | 57 +- .../typingsInstaller/telemetry-events.js | 134 +- .../throttle-delayed-run-install-requests.js | 415 ++- .../throttle-delayed-typings-to-install.js | 217 +- .../external-project-watch-options-errors.js | 126 +- ...ect-watch-options-in-host-configuration.js | 39 +- .../external-project-watch-options.js | 39 +- .../watchEnvironment/files-at-root.js | 73 +- .../files-at-windows-style-root.js | 73 +- .../watchEnvironment/files-not-at-root.js | 73 +- .../files-not-at-windows-style-root.js | 73 +- .../inferred-project-watch-options-errors.js | 65 +- ...ect-watch-options-in-host-configuration.js | 2 +- .../inferred-project-watch-options.js | 2 +- .../project-with-ascii-file-names-with-i.js | 2 +- .../project-with-ascii-file-names.js | 2 +- .../project-with-unicode-file-names.js | 2 +- ...files-starting-with-dot-in-node_modules.js | 88 +- ...polling-when-file-is-added-to-subfolder.js | 96 +- ...rectory-when-file-is-added-to-subfolder.js | 93 +- ...tchFile-when-file-is-added-to-subfolder.js | 84 +- ...watching-files-with-network-style-paths.js | 145 +- ...ere-workspaces-folder-is-hosted-at-root.js | 80 +- ...en-watchFile-is-single-watcher-per-file.js | 76 +- ...excludeDirectories-option-in-configFile.js | 73 +- ...ludeDirectories-option-in-configuration.js | 73 +- ...ackPolling-option-as-host-configuration.js | 73 +- ...th-fallbackPolling-option-in-configFile.js | 73 +- ...hDirectory-option-as-host-configuration.js | 73 +- ...ith-watchDirectory-option-in-configFile.js | 73 +- ...-watchFile-option-as-host-configuration.js | 73 +- .../with-watchFile-option-in-configFile.js | 73 +- 1350 files changed, 148383 insertions(+), 39173 deletions(-) create mode 100644 tests/baselines/reference/tsserver/configFileSearch/should-stop-at-projectRootPath-if-given.js delete mode 100644 tests/baselines/reference/tsserver/configFileSerach/should-stop-at-projectRootPath-if-given.js diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 2e05267fd2dfe..54c7d954ecbb7 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -6,7 +6,7 @@ import * as Utils from "./_namespaces/Utils"; import * as vfs from "./_namespaces/vfs"; import * as vpath from "./_namespaces/vpath"; import { - Logger, + LoggerWithInMemoryLogs, } from "./tsserverLogger"; import ArrayOrSingle = FourSlashInterface.ArrayOrSingle; @@ -259,7 +259,7 @@ export class TestState { private inputFiles = new Map(); // Map between inputFile's fileName and its content for easily looking up when resolving references - private logger: Logger | undefined; + private logger: LoggerWithInMemoryLogs | undefined; private static getDisplayPartsJson(displayParts: ts.SymbolDisplayPart[] | undefined) { let result = ""; @@ -519,7 +519,7 @@ export class TestState { if (this.logger) { Harness.Baseline.runBaseline( `tsserver/fourslashServer/${ts.getBaseFileName(this.originalInputFileName).replace(".ts", ".js")}`, - this.logger.logs!.join("\n"), + this.logger.logs.join("\n"), ); } } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index b5dd1318f5e2c..80ea1e5e87a9d 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -17,7 +17,7 @@ import { import { createLoggerWithInMemoryLogs, HarnessLSCouldNotResolveModule, - Logger, + LoggerWithInMemoryLogs, } from "./tsserverLogger"; import { createWatchUtils, @@ -130,7 +130,7 @@ export interface LanguageServiceAdapter { getLanguageService(): ts.LanguageService; getClassifier(): ts.Classifier; getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo; - getLogger(): Logger | undefined; + getLogger(): LoggerWithInMemoryLogs | undefined; } export abstract class LanguageServiceAdapterHost { @@ -611,7 +611,7 @@ export class ServerLanguageServiceAdapter implements LanguageServiceAdapter { private host: SessionClientHost; private client: ts.server.SessionClient; private server: FourslashSession; - private logger: Logger; + private logger: LoggerWithInMemoryLogs; constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { // This is the main host that tests use to direct tests const clientHost = new SessionClientHost(cancellationToken, options); diff --git a/src/harness/incrementalUtils.ts b/src/harness/incrementalUtils.ts index 742e0a88d8f66..c35ffdd9a697b 100644 --- a/src/harness/incrementalUtils.ts +++ b/src/harness/incrementalUtils.ts @@ -431,6 +431,7 @@ function verifyProgram(service: ts.server.ProjectService, project: ts.server.Pro const getDefaultLibLocation = compilerHost.getDefaultLibLocation!; compilerHost.getDefaultLibLocation = () => ts.getNormalizedAbsolutePath(getDefaultLibLocation(), service.host.getCurrentDirectory()); compilerHost.getDefaultLibFileName = options => ts.combinePaths(compilerHost.getDefaultLibLocation!(), ts.getDefaultLibFileName(options)); + compilerHost.trace = ts.noop; // We dont want to update host just because of trace const readFile = compilerHost.readFile; compilerHost.readFile = fileName => { const path = project.toPath(fileName); diff --git a/src/harness/tsserverLogger.ts b/src/harness/tsserverLogger.ts index ccc166fe1d365..76cca7bd2b4c4 100644 --- a/src/harness/tsserverLogger.ts +++ b/src/harness/tsserverLogger.ts @@ -50,12 +50,16 @@ export function nullLogger(): Logger { } export function createHasErrorMessageLogger(): Logger { - return { - ...nullLogger(), - msg: (s, type) => ts.Debug.fail(`Error: ${s}, type: ${type}`), - }; + const logger = nullLogger(); + logger.msg = (s, type) => ts.Debug.fail(`Error: ${s}, type: ${type}`); + return logger; } -function handleLoggerGroup(logger: Logger): Logger { +function handleLoggerGroup(logger: Logger, host: ts.server.ServerHost, logText: (s: string) => void, sanitizeLibs: true | undefined): Logger { + logger.hasLevel = ts.returnTrue; + logger.loggingEnabled = ts.returnTrue; + logger.host = host; + if (host) logText(`currentDirectory:: ${host.getCurrentDirectory()} useCaseSensitiveFileNames: ${host.useCaseSensitiveFileNames}`); + let inGroup = false; let firstInGroup = false; logger.startGroup = () => { @@ -63,11 +67,14 @@ function handleLoggerGroup(logger: Logger): Logger { firstInGroup = true; }; logger.endGroup = () => inGroup = false; - const originalInfo = logger.info; - logger.info = s => msg(s, ts.server.Msg.Info, s => originalInfo.call(logger, s)); - logger.log = s => originalInfo.call(logger, s); + logger.info = s => msg(s, ts.server.Msg.Info, log); + logger.log = log; return logger; + function log(s: string) { + logText((sanitizeLibs ? sanitizeLibFileText : ts.identity)(sanitizeLog(s))); + } + function msg(s: string, type = ts.server.Msg.Err, write: (s: string) => void) { s = `[${nowString(logger)}] ${s}`; if (!inGroup || firstInGroup) s = padStringRight(type + " seq", " ") + s; @@ -86,16 +93,19 @@ export function nowString(logger: Logger) { return `hh:mm:ss:mss`; } -export function createLoggerWritingToConsole(host: ts.server.ServerHost) { - return handleLoggerGroup({ - ...nullLogger(), - hasLevel: ts.returnTrue, - loggingEnabled: ts.returnTrue, - perftrc: s => console.log(s), - info: s => console.log(s), - msg: (s, type) => console.log(`${type}:: ${s}`), +export function createLoggerWritingToConsole(host: ts.server.ServerHost, sanitizeLibs?: true) { + const logger = createHasErrorMessageLogger(); + logger.logs = []; + logger.logs.push = (...args) => { + args.forEach(s => console.log(s)); + return 0; + }; + return handleLoggerGroup( + logger, host, - }); + s => console.log(s), + sanitizeLibs, + ) as LoggerWithInMemoryLogs; } export function sanitizeLog(s: string): string { @@ -117,6 +127,7 @@ export function sanitizeLog(s: string): string { s = s.replace(/"exportMapKey":\s*"\d+ \d+ /g, match => match.replace(/ \d+ /, ` * `)); s = s.replace(/getIndentationAtPosition: getCurrentSourceFile: \d+(?:\.\d+)?/, `getIndentationAtPosition: getCurrentSourceFile: *`); s = s.replace(/getIndentationAtPosition: computeIndentation\s*: \d+(?:\.\d+)?/, `getIndentationAtPosition: computeIndentation: *`); + s = replaceAll(s, `@ts${ts.versionMajorMinor}`, `@tsFakeMajor.Minor`); s = sanitizeHarnessLSException(s); return s; } @@ -135,16 +146,12 @@ export function sanitizeLibFileText(s: string): string { return s; } -export function createLoggerWithInMemoryLogs(host: ts.server.ServerHost, sanitizeLibs?: true): Logger { +export interface LoggerWithInMemoryLogs extends Logger { + logs: string[]; +} + +export function createLoggerWithInMemoryLogs(host: ts.server.ServerHost, sanitizeLibs?: true): LoggerWithInMemoryLogs { const logger = createHasErrorMessageLogger(); - const logs: string[] = []; - if (host) logs.push(`currentDirectory:: ${host.getCurrentDirectory()} useCaseSensitiveFileNames: ${host.useCaseSensitiveFileNames}`); - return handleLoggerGroup({ - ...logger, - logs, - hasLevel: ts.returnTrue, - loggingEnabled: ts.returnTrue, - info: s => logs.push((sanitizeLibs ? sanitizeLibFileText : ts.identity)(sanitizeLog(s))), - host, - }); + logger.logs = []; + return handleLoggerGroup(logger, host, s => logger.logs!.push(s), sanitizeLibs) as LoggerWithInMemoryLogs; } diff --git a/src/server/session.ts b/src/server/session.ts index e97e5ca86a314..3b816498ddb42 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1131,9 +1131,10 @@ export class Session implements EventSender { } private projectsUpdatedInBackgroundEvent(openFiles: string[]): void { - this.projectService.logger.info(`got projects updated in background, updating diagnostics for ${openFiles}`); + this.projectService.logger.info(`got projects updated in background ${openFiles}`); if (openFiles.length) { if (!this.suppressDiagnosticEvents && !this.noGetErrOnBackgroundUpdate) { + this.projectService.logger.info(`Queueing diagnostics update for ${openFiles}`); // For now only queue error checking for open files. We can change this to include non open files as well this.errorCheck.startNew(next => this.updateErrorCheck(next, openFiles, 100, /*requireOpen*/ true)); } @@ -1193,7 +1194,7 @@ export class Session implements EventSender { public send(msg: protocol.Message) { if (msg.type === "event" && !this.canUseEvents) { if (this.logger.hasLevel(LogLevel.verbose)) { - this.logger.info(`Session does not support events: ignored event: ${JSON.stringify(msg)}`); + this.logger.info(`Session does not support events: ignored event: ${stringifyIndented(msg)}`); } return; } diff --git a/src/testRunner/unittests/helpers/tscWatch.ts b/src/testRunner/unittests/helpers/tscWatch.ts index c642863b9e057..a16b2bd8d93a5 100644 --- a/src/testRunner/unittests/helpers/tscWatch.ts +++ b/src/testRunner/unittests/helpers/tscWatch.ts @@ -20,6 +20,8 @@ import { import { changeToHostTrackingWrittenFiles, File, + SerializeOutputOrder, + StateLogger, TestServerHost, TestServerHostTrackingWrittenFiles, } from "./virtualFileSystemWithWatch"; @@ -63,13 +65,12 @@ export interface TscWatchCompile extends TscWatchCompileBase { export const noopChange: TscWatchCompileChange = { caption: "No change", edit: ts.noop, - timeouts: sys => sys.logTimeoutQueueLength(), + timeouts: ts.noop, }; -export type SystemSnap = ReturnType; function tscWatchCompile(input: TscWatchCompile) { it("tsc-watch:: Generates files matching the baseline", () => { - const { sys, baseline, oldSnap } = createBaseline(input.sys()); + const { sys, baseline } = createBaseline(input.sys()); const { scenario, subScenario, @@ -92,7 +93,6 @@ function tscWatchCompile(input: TscWatchCompile) { commandLineArgs, sys, baseline, - oldSnap, getPrograms, baselineSourceMap, baselineDependencies, @@ -102,44 +102,21 @@ function tscWatchCompile(input: TscWatchCompile) { }); } -export interface TestServerHostWithTimeoutLogging { - logTimeoutQueueLength(): void; -} - -export type TscWatchSystem = TestServerHostTrackingWrittenFiles & TestServerHostWithTimeoutLogging; +export type TscWatchSystem = TestServerHostTrackingWrittenFiles; -function changeToTestServerHostWithTimeoutLogging(inputHost: TestServerHostTrackingWrittenFiles, baseline: string[]): TscWatchSystem { - const host = inputHost as TscWatchSystem; - const originalRunQueuedTimeoutCallbacks = host.runQueuedTimeoutCallbacks; - const originalRunQueuedImmediateCallbacks = host.runQueuedImmediateCallbacks; - host.runQueuedTimeoutCallbacks = runQueuedTimeoutCallbacks; - host.runQueuedImmediateCallbacks = runQueuedImmediateCallbacks; - host.logTimeoutQueueLength = logTimeoutQueueLength; +function changeToTestServerHostWithTimeoutLogging(host: TestServerHostTrackingWrittenFiles, baseline: string[]): TscWatchSystem { + const logger: StateLogger = { + log: s => baseline.push(s), + logs: baseline, + }; + host.timeoutCallbacks.switchToBaseliningInvoke(logger, SerializeOutputOrder.BeforeDiff); + host.immediateCallbacks.switchToBaseliningInvoke(logger, SerializeOutputOrder.BeforeDiff); return host; - - function logTimeoutQueueLength() { - baseline.push(host.timeoutCallbacks.log()); - baseline.push(host.immediateCallbacks.log()); - } - - function runQueuedTimeoutCallbacks(timeoutId?: number) { - baseline.push(`Before running ${host.timeoutCallbacks.log()}`); - if (timeoutId !== undefined) baseline.push(`Invoking ${host.timeoutCallbacks.callbackType} callback:: timeoutId:: ${timeoutId}:: ${host.timeoutCallbacks.map[timeoutId].args[0]}`); - originalRunQueuedTimeoutCallbacks.call(host, timeoutId); - baseline.push(`After running ${host.timeoutCallbacks.log()}`); - } - - function runQueuedImmediateCallbacks() { - baseline.push(`Before running ${host.immediateCallbacks.log()}`); - originalRunQueuedImmediateCallbacks.call(host); - baseline.push(`After running ${host.immediateCallbacks.log()}`); - } } export interface BaselineBase { baseline: string[]; sys: TscWatchSystem; - oldSnap: SystemSnap; } export interface Baseline extends BaselineBase, CommandLineCallbacks { @@ -153,9 +130,9 @@ export function createBaseline(system: TestServerHost, modifySystem?: (sys: Test const sys = changeToTestServerHostWithTimeoutLogging(changeToHostTrackingWrittenFiles(initialSys), baseline); baseline.push(`currentDirectory:: ${sys.getCurrentDirectory()} useCaseSensitiveFileNames: ${sys.useCaseSensitiveFileNames}`); baseline.push("Input::"); - sys.diff(baseline); + sys.serializeState(baseline, SerializeOutputOrder.None); const { cb, getPrograms } = commandLineCallbacks(sys); - return { sys, baseline, oldSnap: sys.snap(), cb, getPrograms }; + return { sys, baseline, cb, getPrograms }; } export function createSolutionBuilderWithWatchHostForBaseline(sys: TestServerHost, cb: ts.ExecuteCommandLineCallbacks) { @@ -208,13 +185,10 @@ function updateWatchHostForBaseline(host: ts.WatchC } export function applyEdit(sys: BaselineBase["sys"], baseline: BaselineBase["baseline"], edit: TscWatchCompileChange["edit"], caption?: TscWatchCompileChange["caption"]) { - const oldSnap = sys.snap(); baseline.push(`Change::${caption ? " " + caption : ""}`, ""); edit(sys); baseline.push("Input::"); - sys.diff(baseline, oldSnap); - sys.serializeWatches(baseline); - return sys.snap(); + sys.serializeState(baseline, SerializeOutputOrder.AfterDiff); } export interface RunWatchBaseline extends BaselineBase, TscWatchCompileBase { @@ -230,7 +204,6 @@ export function runWatchBaseline { assert.equal(value, 1, `Expected to write file ${key} only once`); }); + sys.serializeState(baseline, SerializeOutputOrder.BeforeDiff); + baselinePrograms(baseline, programs, oldPrograms, baselineDependencies); + baseline.push(`exitCode:: ExitStatus.${ts.ExitStatus[sys.exitCode as ts.ExitStatus]}`, ""); // Verify program structure and resolution cache when incremental edit with tsc --watch (without build mode) if (resolutionCache && programs.length) { ts.Debug.assert(programs.length === 1); verifyProgramStructureAndResolutionCache(caption!, sys, programs[0][0], resolutionCache, useSourceOfProjectReferenceRedirect, symlinksNotReflected); } - sys.writtenFiles.clear(); return programs; } function verifyProgramStructureAndResolutionCache( diff --git a/src/testRunner/unittests/helpers/tsserver.ts b/src/testRunner/unittests/helpers/tsserver.ts index e88d01170f603..afe58b1c02d1f 100644 --- a/src/testRunner/unittests/helpers/tsserver.ts +++ b/src/testRunner/unittests/helpers/tsserver.ts @@ -2,9 +2,8 @@ import { incrementalVerifier, } from "../../../harness/incrementalUtils"; import { - createHasErrorMessageLogger, createLoggerWithInMemoryLogs, - Logger, + LoggerWithInMemoryLogs, } from "../../../harness/tsserverLogger"; import * as Harness from "../../_namespaces/Harness"; import * as ts from "../../_namespaces/ts"; @@ -22,12 +21,13 @@ import { File, FileOrFolderOrSymLink, libFile, + SerializeOutputOrder, + StateLogger, TestServerHost, TestServerHostTrackingWrittenFiles, } from "./virtualFileSystemWithWatch"; -export function baselineTsserverLogs(scenario: string, subScenario: string, sessionOrService: { logger: Logger; }) { - ts.Debug.assert(sessionOrService.logger.logs?.length); // Ensure caller used in memory logger +export function baselineTsserverLogs(scenario: string, subScenario: string, sessionOrService: { logger: LoggerWithInMemoryLogs; }) { Harness.Baseline.runBaseline(`tsserver/${scenario}/${subScenario.split(" ").join("-")}.js`, sessionOrService.logger.logs.join("\r\n")); } @@ -49,29 +49,24 @@ export function toExternalFiles(fileNames: string[]) { export type TestSessionAndServiceHost = TestServerHostTrackingWrittenFiles & { patched: boolean; baselineHost(title: string): void; - logTimeoutQueueLength(): void; }; export function patchHostTimeouts( inputHost: TestServerHostTrackingWrittenFiles, - logger: Logger, + logger: LoggerWithInMemoryLogs, ) { const host = inputHost as TestSessionAndServiceHost; if (host.patched) return host; host.patched = true; if (!logger.hasLevel(ts.server.LogLevel.verbose)) { - host.logTimeoutQueueLength = ts.notImplemented; host.baselineHost = ts.notImplemented; return host; } - const originalRunQueuedTimeoutCallbacks = host.runQueuedTimeoutCallbacks; - const originalRunQueuedImmediateCallbacks = host.runQueuedImmediateCallbacks; const originalSetTime = host.setTime; - let hostDiff: ReturnType | undefined; - host.runQueuedTimeoutCallbacks = runQueuedTimeoutCallbacks; - host.runQueuedImmediateCallbacks = runQueuedImmediateCallbacks; - host.logTimeoutQueueLength = logTimeoutQueueLength; + host.timeoutCallbacks.switchToBaseliningInvoke(logger, SerializeOutputOrder.None); + host.immediateCallbacks.switchToBaseliningInvoke(logger as StateLogger, SerializeOutputOrder.None); + host.pendingInstalls.switchToBaseliningInvoke(logger, SerializeOutputOrder.None); host.setTime = setTime; host.baselineHost = baselineHost; host.patched = true; @@ -82,52 +77,63 @@ export function patchHostTimeouts( return originalSetTime.call(host, time); } - function logTimeoutQueueLength() { - logger.log(host.timeoutCallbacks.log()); - host.baselineHost(host.immediateCallbacks.log()); - } - - function runQueuedTimeoutCallbacks(timeoutId?: number) { - host.baselineHost(`Before running ${host.timeoutCallbacks.log()}`); - if (timeoutId !== undefined) logger.log(`Invoking ${host.timeoutCallbacks.callbackType} callback:: timeoutId:: ${timeoutId}:: ${host.timeoutCallbacks.map[timeoutId].args[0]}`); - originalRunQueuedTimeoutCallbacks.call(host, timeoutId); - host.baselineHost(`After running ${host.timeoutCallbacks.log()}`); - } - - function runQueuedImmediateCallbacks() { - host.baselineHost(`Before running ${host.immediateCallbacks.log()}`); - originalRunQueuedImmediateCallbacks.call(host); - host.baselineHost(`After running ${host.immediateCallbacks.log()}`); - } - function baselineHost(title: string) { logger.log(title); - ts.Debug.assertIsDefined(logger.logs); - host.diff(logger.logs, hostDiff); - host.serializeWatches(logger.logs); - hostDiff = host.snap(); - host.writtenFiles.clear(); + host.serializeState(logger.logs, SerializeOutputOrder.None); } } export interface TestSessionOptions extends ts.server.SessionOptions, TestTypingsInstallerOptions { - logger: Logger; - allowNonBaseliningLogger?: boolean; + host: TestServerHost; + logger: LoggerWithInMemoryLogs; disableAutomaticTypingAcquisition?: boolean; + useCancellationToken?: boolean | number; } - +export type TestSessionPartialOptionsAndHost = Partial> & Pick; +export type TestSessionConstructorOptions = TestServerHost | TestSessionPartialOptionsAndHost; export type TestSessionRequest = Pick; + +function getTestSessionPartialOptionsAndHost(optsOrHost: TestSessionConstructorOptions): TestSessionPartialOptionsAndHost { + // eslint-disable-next-line local/no-in-operator + return "host" in optsOrHost ? + optsOrHost : + { host: optsOrHost }; +} export class TestSession extends ts.server.Session { private seq = 0; - public testhost: TestSessionAndServiceHost; - public override logger: Logger; - - constructor(opts: TestSessionOptions) { - super(opts); - this.logger = opts.logger; - ts.Debug.assert(opts.allowNonBaseliningLogger || this.logger.hasLevel(ts.server.LogLevel.verbose), "Use Baselining logger and baseline tsserver log or create using allowNonBaseliningLogger"); - this.testhost = patchHostTimeouts( - changeToHostTrackingWrittenFiles(this.host as TestServerHost), + public override host!: TestSessionAndServiceHost; + public override logger!: LoggerWithInMemoryLogs; + public override readonly typingsInstaller!: TestTypingsInstaller; + public serverCancellationToken: TestServerCancellationToken; + + constructor(optsOrHost: TestSessionConstructorOptions) { + const opts = getTestSessionPartialOptionsAndHost(optsOrHost); + opts.logger = opts.logger || createLoggerWithInMemoryLogs(opts.host); + const typingsInstaller = !opts.disableAutomaticTypingAcquisition ? new TestTypingsInstaller(opts) : undefined; + const cancellationToken = opts.useCancellationToken ? + new TestServerCancellationToken( + opts.logger, + ts.isNumber(opts.useCancellationToken) ? opts.useCancellationToken : undefined, + ) : + ts.server.nullCancellationToken; + super({ + cancellationToken, + useSingleInferredProject: false, + useInferredProjectPerProjectRoot: false, + noGetErrOnBackgroundUpdate: true, + byteLength: Buffer.byteLength, + hrtime: process.hrtime, + logger: opts.logger, + canUseEvents: true, + incrementalVerifier, + typesMapLocation: customTypesMap.path, + typingsInstaller, + ...opts, + }); + if (typingsInstaller) typingsInstaller.session = this; + this.serverCancellationToken = cancellationToken as TestServerCancellationToken; + patchHostTimeouts( + changeToHostTrackingWrittenFiles(this.host), this.logger, ); } @@ -146,13 +152,13 @@ export class TestSession extends ts.server.Session { public override executeCommand(request: ts.server.protocol.Request) { if (this.logger.hasLevel(ts.server.LogLevel.verbose)) { - this.testhost.baselineHost("Before request"); + this.host.baselineHost("Before request"); this.logger.info(`request:${ts.server.stringifyIndented(request)}`); } const response = super.executeCommand(request); if (this.logger.hasLevel(ts.server.LogLevel.verbose)) { this.logger.info(`response:${ts.server.stringifyIndented(response.response === ts.getSupportedCodeFixes() ? { ...response, response: "ts.getSupportedCodeFixes()" } : response)}`); - this.testhost.baselineHost("After request"); + this.host.baselineHost("After request"); } return response; } @@ -166,37 +172,13 @@ export class TestSession extends ts.server.Session { } } -export function createSession(host: TestServerHost, opts: Partial = {}) { - const logger = opts.logger || createHasErrorMessageLogger(); - if (!opts.disableAutomaticTypingAcquisition && opts.typingsInstaller === undefined) { - opts.typingsInstaller = new TestTypingsInstaller( - host, - logger, - opts, - ); - } - - if (opts.eventHandler !== undefined) { - opts.canUseEvents = true; - } - - const sessionOptions: TestSessionOptions = { - host, - cancellationToken: ts.server.nullCancellationToken, - useSingleInferredProject: false, - useInferredProjectPerProjectRoot: false, - byteLength: Buffer.byteLength, - hrtime: process.hrtime, - logger, - canUseEvents: false, - incrementalVerifier, - }; - - return new TestSession({ ...sessionOptions, ...opts }); -} - -export function createSessionWithCustomEventHandler(host: TestServerHost, opts?: Partial, customAction?: (event: ts.server.ProjectServiceEvent) => void) { - const session = createSession(host, { eventHandler, logger: createLoggerWithInMemoryLogs(host), ...opts }); +export function createSessionWithCustomEventHandler( + optsOrHost: TestSessionConstructorOptions, + customAction?: (event: ts.server.ProjectServiceEvent) => void, +) { + const opts = getTestSessionPartialOptionsAndHost(optsOrHost); + opts.eventHandler = eventHandler; + const session = new TestSession(opts); return session; function eventHandler(event: ts.server.ProjectServiceEvent) { let data = event.data as any; @@ -228,42 +210,6 @@ export function createSessionWithCustomEventHandler(host: TestServerHost, opts?: } } -export interface TestProjectServiceOptions extends ts.server.ProjectServiceOptions { - logger: Logger; - allowNonBaseliningLogger?: boolean; -} - -export class TestProjectService extends ts.server.ProjectService { - public testhost: TestSessionAndServiceHost; - constructor(host: TestServerHost, public override logger: Logger, cancellationToken: ts.HostCancellationToken, useSingleInferredProject: boolean, typingsInstaller: ts.server.ITypingsInstaller, opts: Partial = {}) { - super({ - host, - logger, - session: undefined, - cancellationToken, - useSingleInferredProject, - useInferredProjectPerProjectRoot: false, - typingsInstaller, - typesMapLocation: customTypesMap.path, - incrementalVerifier, - ...opts, - }); - ts.Debug.assert(opts.allowNonBaseliningLogger || this.logger.hasLevel(ts.server.LogLevel.verbose), "Use Baselining logger and baseline tsserver log or create using allowNonBaseliningLogger"); - this.testhost = patchHostTimeouts( - changeToHostTrackingWrittenFiles(this.host as TestServerHost), - this.logger, - ); - if (logger.hasLevel(ts.server.LogLevel.verbose)) this.testhost.baselineHost("Creating project service"); - } -} - -export function createProjectService(host: TestServerHost, options?: Partial) { - const cancellationToken = options?.cancellationToken || ts.server.nullCancellationToken; - const logger = options?.logger || createHasErrorMessageLogger(); - const useSingleInferredProject = options?.useSingleInferredProject !== undefined ? options.useSingleInferredProject : false; - return new TestProjectService(host, logger, cancellationToken, useSingleInferredProject, options?.typingsInstaller || ts.server.nullTypingsInstaller, options); -} - export function protocolLocationFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): ts.server.protocol.Location { const start = nthIndexOf(str, substring, options ? options.index : 0); ts.Debug.assert(start !== -1); @@ -318,11 +264,12 @@ export class TestServerCancellationToken implements ts.server.ServerCancellation private requestToCancel = -1; private isCancellationRequestedCount = 0; - constructor(private logger: Logger, private cancelAfterRequest = 0) { + constructor(private logger: LoggerWithInMemoryLogs, private cancelAfterRequest = 0) { } setRequest(requestId: number) { this.currentId = requestId; + this.logger.log(`TestServerCancellationToken:: Cancellation Request id:: ${requestId}`); } setRequestToCancel(requestId: number) { @@ -415,7 +362,7 @@ export function setCompilerOptionsForInferredProjectsRequestForSession( }); } -export function logDiagnostics(sessionOrService: TestSession | TestProjectService, diagnosticsType: string, project: ts.server.Project, diagnostics: readonly ts.Diagnostic[]) { +export function logDiagnostics(sessionOrService: TestSession, diagnosticsType: string, project: ts.server.Project, diagnostics: readonly ts.Diagnostic[]) { sessionOrService.logger.info(`${diagnosticsType}:: ${diagnostics.length}`); diagnostics.forEach(d => sessionOrService.logger.info(ts.formatDiagnostic(d, project))); } @@ -445,11 +392,10 @@ export interface CheckAllErrors extends VerifyGetErrRequestBase { skip?: readonly (SkipErrors | undefined)[]; } function checkAllErrors({ session, existingTimeouts, files, skip }: CheckAllErrors) { - ts.Debug.assert(session.logger.logs?.length); for (let i = 0; i < files.length; i++) { - session.testhost.runQueuedTimeoutCallbacks(existingTimeouts ? session.testhost.getNextTimeoutId() - 1 : undefined); - if (!skip?.[i]?.semantic) session.testhost.runQueuedImmediateCallbacks(); - if (!skip?.[i]?.suggestion) session.testhost.runQueuedImmediateCallbacks(); + session.host.runQueuedTimeoutCallbacks(existingTimeouts ? session.host.getNextTimeoutId() - 1 : undefined); + if (!skip?.[i]?.semantic) session.host.runQueuedImmediateCallbacks(); + if (!skip?.[i]?.suggestion) session.host.runQueuedImmediateCallbacks(); } } @@ -460,7 +406,7 @@ function filePath(file: string | File) { function verifyErrorsUsingGeterr({ scenario, subScenario, allFiles, openFiles, getErrRequest }: VerifyGetErrScenario) { it("verifies the errors in open file", () => { const host = createServerHost([...allFiles(), libFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession(openFiles(), session); verifyGetErrRequest({ session, files: getErrRequest() }); @@ -471,7 +417,7 @@ function verifyErrorsUsingGeterr({ scenario, subScenario, allFiles, openFiles, g function verifyErrorsUsingGeterrForProject({ scenario, subScenario, allFiles, openFiles, getErrForProjectRequest }: VerifyGetErrScenario) { it("verifies the errors in projects", () => { const host = createServerHost([...allFiles(), libFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession(openFiles(), session); for (const expected of getErrForProjectRequest()) { @@ -488,7 +434,7 @@ function verifyErrorsUsingGeterrForProject({ scenario, subScenario, allFiles, op function verifyErrorsUsingSyncMethods({ scenario, subScenario, allFiles, openFiles, syncDiagnostics }: VerifyGetErrScenario) { it("verifies the errors using sync commands", () => { const host = createServerHost([...allFiles(), libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession(openFiles(), session); for (const { file, project } of syncDiagnostics()) { const reqArgs = { file: filePath(file), projectFileName: project && filePath(project) }; @@ -533,8 +479,8 @@ export function verifyGetErrScenario(scenario: VerifyGetErrScenario) { verifyErrorsUsingSyncMethods(scenario); } -export function verifyDynamic(service: ts.server.ProjectService, path: string) { - (service.logger as Logger).log(`${path} isDynamic:: ${service.filenameToScriptInfo.get(path)!.isDynamic}`); +export function verifyDynamic(session: TestSession, path: string) { + session.logger.log(`${path} isDynamic:: ${session.getProjectService().filenameToScriptInfo.get(path)!.isDynamic}`); } export function createHostWithSolutionBuild(files: readonly FileOrFolderOrSymLink[], rootNames: readonly string[]) { @@ -544,10 +490,10 @@ export function createHostWithSolutionBuild(files: readonly FileOrFolderOrSymLin return host; } -export function logInferredProjectsOrphanStatus(projectService: ts.server.ProjectService) { - projectService.inferredProjects.forEach(inferredProject => (projectService.logger as Logger).log(`Inferred project: ${inferredProject.projectName} isOrphan:: ${inferredProject.isOrphan()} isClosed: ${inferredProject.isClosed()}`)); +export function logInferredProjectsOrphanStatus(session: TestSession) { + session.getProjectService().inferredProjects.forEach(inferredProject => session.logger.log(`Inferred project: ${inferredProject.projectName} isOrphan:: ${inferredProject.isOrphan()} isClosed: ${inferredProject.isClosed()}`)); } -export function logConfiguredProjectsHasOpenRefStatus(projectService: ts.server.ProjectService) { - projectService.configuredProjects.forEach(configuredProject => (projectService.logger as Logger).log(`Configured project: ${configuredProject.projectName} hasOpenRef:: ${configuredProject.hasOpenRef()} isClosed: ${configuredProject.isClosed()}`)); +export function logConfiguredProjectsHasOpenRefStatus(session: TestSession) { + session.getProjectService().configuredProjects.forEach(configuredProject => session.logger.log(`Configured project: ${configuredProject.projectName} hasOpenRef:: ${configuredProject.hasOpenRef()} isClosed: ${configuredProject.isClosed()}`)); } diff --git a/src/testRunner/unittests/helpers/typingsInstaller.ts b/src/testRunner/unittests/helpers/typingsInstaller.ts index 025ebe4b84ce2..8553c20c72745 100644 --- a/src/testRunner/unittests/helpers/typingsInstaller.ts +++ b/src/testRunner/unittests/helpers/typingsInstaller.ts @@ -1,23 +1,24 @@ import { - Logger, + LoggerWithInMemoryLogs, nowString, - replaceAll, - sanitizeLog, } from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { + ActionInvalidate, + ActionPackageInstalled, + ActionSet, ActionWatchTypingLocations, + EventBeginInstallTypes, + EventEndInstallTypes, stringifyIndented, } from "../../_namespaces/ts.server"; import { jsonToReadableText, } from "../helpers"; import { - patchHostTimeouts, - TestSessionAndServiceHost, + TestSession, } from "./tsserver"; import { - changeToHostTrackingWrittenFiles, File, TestServerHost, } from "./virtualFileSystemWithWatch"; @@ -46,38 +47,19 @@ export const customTypesMap = { }`, }; -export interface PostExecAction { - readonly success: boolean; - requestId: number; - readonly packageNames: readonly string[]; - readonly callback: ts.server.typingsInstaller.RequestCompletedAction; -} -export function loggerToTypingsInstallerLog(logger: Logger): ts.server.typingsInstaller.Log | undefined { - return logger?.loggingEnabled() ? { +export function loggerToTypingsInstallerLog(logger: LoggerWithInMemoryLogs): ts.server.typingsInstaller.Log { + ts.Debug.assert(logger.loggingEnabled()); + return { isEnabled: ts.returnTrue, - writeLine: s => { - // This is a VERY VERY NAIVE sanitization strategy. - // If a substring containing the exact TypeScript version is found, - // even if it's unrelated to TypeScript itself, then it will be replaced, - // leaving us with two options: - // - // 1. Deal with flip-flopping baselines. - // 2. Change the TypeScript version until no matching substring is found. - // - const initialLog = sanitizeLog(s); - const pseudoSanitizedLog = replaceAll(initialLog, `@ts${ts.versionMajorMinor}`, `@tsFakeMajor.Minor`); - return logger.log(`TI:: [${nowString(logger)}] ${pseudoSanitizedLog}`); - }, - } : undefined; + writeLine: s => logger.log(`TI:: [${nowString(logger)}] ${s}`), + }; } interface TypesRegistryFile { entries: ts.MapLike>; } function loadTypesRegistryFile(typesRegistryFilePath: string, host: TestServerHost, log: ts.server.typingsInstaller.Log): Map> { if (!host.fileExists(typesRegistryFilePath)) { - if (log.isEnabled()) { - log.writeLine(`Types registry file '${typesRegistryFilePath}' does not exist`); - } + log.writeLine(`Types registry file '${typesRegistryFilePath}' does not exist`); return new Map>(); } try { @@ -85,9 +67,7 @@ function loadTypesRegistryFile(typesRegistryFilePath: string, host: TestServerHo return new Map(Object.entries(content.entries)); } catch (e) { - if (log.isEnabled()) { - log.writeLine(`Error when loading types registry file '${typesRegistryFilePath}': ${(e as Error).message}, ${(e as Error).stack}`); - } + log.writeLine(`Error when loading types registry file '${typesRegistryFilePath}': ${(e as Error).message}, ${(e as Error).stack}`); return new Map>(); } } @@ -97,161 +77,121 @@ function getTypesRegistryFileLocation(globalTypingsCacheLocation: string): strin } export interface FileWithPackageName extends File { - package: string; + package?: string; } export type InstallActionThrowingError = string; -export type InstallActionWithTypingFiles = [installedTypings: string[] | string, typingFiles: File[]]; -export type InstallActionWithFilteredTypings = [typingFiles: FileWithPackageName[]]; -export type InstallAction = InstallActionThrowingError | InstallActionWithTypingFiles | InstallActionWithFilteredTypings; +export type InstallActionWithSuccess = boolean; +export type InstallActionWithTypingFiles = readonly FileWithPackageName[]; +export type InstallAction = InstallActionThrowingError | InstallActionWithSuccess | InstallActionWithTypingFiles; +export type PendingInstallCallback = ( + pendingInstallInfo: string, + installedTypingsOrSuccess: string[] | string | boolean, + typingFiles: readonly File[], + onRequestCompleted: ts.server.typingsInstaller.RequestCompletedAction, +) => void; export class TestTypingsInstallerWorker extends ts.server.typingsInstaller.TypingsInstaller { readonly typesRegistry: Map>; - protected projectService!: ts.server.ProjectService; - constructor( - readonly globalTypingsCacheLocation: string, - throttleLimit: number, - installTypingHost: TestServerHost, - logger: Logger, - typesRegistry: string | readonly string[] | undefined, - private installAction: InstallAction | undefined, - ) { - const log = loggerToTypingsInstallerLog(logger); - if (log?.isEnabled()) { - patchHostTimeouts( - changeToHostTrackingWrittenFiles(installTypingHost), - logger, - ); - (installTypingHost as TestSessionAndServiceHost).baselineHost("TI:: Creating typing installer"); - } + constructor(readonly testTypingInstaller: TestTypingsInstaller) { + const log = loggerToTypingsInstallerLog(testTypingInstaller.session.logger); + ts.Debug.assert(testTypingInstaller.session.host.patched); + testTypingInstaller.session.host.baselineHost("TI:: Creating typing installer"); super( - installTypingHost, - globalTypingsCacheLocation, + testTypingInstaller.session.host, + testTypingInstaller.globalTypingsCacheLocation, "/safeList.json" as ts.Path, customTypesMap.path, - throttleLimit, + testTypingInstaller.throttleLimit, log, ); - this.ensurePackageDirectoryExists(globalTypingsCacheLocation); + this.ensurePackageDirectoryExists(testTypingInstaller.globalTypingsCacheLocation); - if (this.log.isEnabled()) { - this.log.writeLine(`Updating ${typesRegistryPackageName} npm package...`); - this.log.writeLine(`npm install --ignore-scripts ${typesRegistryPackageName}@${this.latestDistTag}`); - } - installTypingHost.ensureFileOrFolder({ - path: getTypesRegistryFileLocation(globalTypingsCacheLocation), + this.log.writeLine(`Updating ${typesRegistryPackageName} npm package...`); + this.log.writeLine(`npm install --ignore-scripts ${typesRegistryPackageName}@${this.latestDistTag}`); + testTypingInstaller.session.host.ensureFileOrFolder({ + path: getTypesRegistryFileLocation(testTypingInstaller.globalTypingsCacheLocation), content: jsonToReadableText(createTypesRegistryFileContent( - typesRegistry ? - ts.isString(typesRegistry) ? - [typesRegistry] : - typesRegistry : + testTypingInstaller.typesRegistry ? + ts.isString(testTypingInstaller.typesRegistry) ? + [testTypingInstaller.typesRegistry] : + testTypingInstaller.typesRegistry : ts.emptyArray, )), }); - if (this.log.isEnabled()) { - this.log.writeLine(`TI:: Updated ${typesRegistryPackageName} npm package`); - } - this.typesRegistry = loadTypesRegistryFile(getTypesRegistryFileLocation(globalTypingsCacheLocation), installTypingHost, this.log); - if (this.log.isEnabled()) { - (installTypingHost as TestSessionAndServiceHost).baselineHost("TI:: typing installer creation complete"); - } - } - - protected postExecActions: PostExecAction[] = []; - - executePendingCommands() { - const actionsToRun = this.postExecActions; - this.postExecActions = []; - for (const action of actionsToRun) { - if (this.log.isEnabled()) { - this.log.writeLine(`#${action.requestId} with arguments'${jsonToReadableText(action.packageNames)}':: ${action.success}`); - } - action.callback(action.success); - } - } + this.log.writeLine(`Updated ${typesRegistryPackageName} npm package`); - attach(projectService: ts.server.ProjectService) { - this.projectService = projectService; - } - - getInstallTypingHost() { - return this.installTypingHost; + this.typesRegistry = loadTypesRegistryFile( + getTypesRegistryFileLocation(testTypingInstaller.globalTypingsCacheLocation), + testTypingInstaller.session.host, + this.log, + ); + testTypingInstaller.session.host.baselineHost("TI:: typing installer creation complete"); } - installWorker(requestId: number, packageNames: string[], _cwd: string, cb: ts.server.typingsInstaller.RequestCompletedAction): void { - if (this.log.isEnabled()) { - this.log.writeLine(`#${requestId} with arguments'${jsonToReadableText(packageNames)}'.`); - } - if (!this.installAction) { - this.addPostExecAction("success", requestId, packageNames, cb); - } - else if (ts.isString(this.installAction)) { - assert(false, this.installAction); - } - else if (this.installAction.length === 2) { - this.executeInstallWithTypingFiles( + installWorker(requestId: number, packageNames: string[], cwd: string, onRequestCompleted: ts.server.typingsInstaller.RequestCompletedAction): void { + this.log.writeLine(`#${requestId} with cwd: ${cwd} arguments: ${jsonToReadableText(packageNames)}`); + if (typeof this.testTypingInstaller.installAction === "boolean") { + this.scheduleInstall( requestId, packageNames, - this.installAction[0], - this.installAction[1], - cb, + this.testTypingInstaller.installAction, + ts.emptyArray, + onRequestCompleted, ); } + else if (ts.isString(this.testTypingInstaller.installAction)) { + assert(false, this.testTypingInstaller.installAction); + } else { - const typingFiles = this.installAction[0].filter(f => packageNames.includes(ts.server.typingsInstaller.typingsName(f.package))); - this.executeInstallWithTypingFiles( + const typingFiles = this.testTypingInstaller.installAction.filter(f => !f.package || packageNames.includes(ts.server.typingsInstaller.typingsName(f.package))); + this.scheduleInstall( requestId, packageNames, - typingFiles.map(f => f.package), + /*success*/ true, typingFiles, - cb, + onRequestCompleted, ); } } - executeInstallWithTypingFiles( + private scheduleInstall( requestId: number, packageNames: string[], - installedTypings: string[] | string, - typingFiles: File[], - cb: ts.server.typingsInstaller.RequestCompletedAction, + success: boolean, + typingFiles: readonly File[], + onRequestCompleted: ts.server.typingsInstaller.RequestCompletedAction, ): void { - this.addPostExecAction(installedTypings, requestId, packageNames, success => { - const host = this.getInstallTypingHost() as TestSessionAndServiceHost; - host.baselineHost("TI:: Before installWorker"); - for (const file of typingFiles) { - host.ensureFileOrFolder(file); - } - host.baselineHost("TI:: After installWorker"); - cb(success); - }); + this.testTypingInstaller.session.host.scheduleInstall( + pendingInstallInfo => { + for (const file of typingFiles) { + this.testTypingInstaller.session.host.ensureFileOrFolder(file); + } + this.testTypingInstaller.session.host.baselineHost(`TI:: Installation ${pendingInstallInfo} complete with success::${!!success}`); + onRequestCompleted(!!success); + }, + `#${requestId} with arguments:: ${jsonToReadableText(packageNames)}`, + ); } - sendResponse(response: ts.server.SetTypings | ts.server.InvalidateCachedTypings | ts.server.WatchTypingLocations) { - if (this.log.isEnabled()) { - this.log.writeLine(`Sending response:${stringifyIndented(response)}`); - } - if (response.kind !== ActionWatchTypingLocations) this.projectService.updateTypingsForProject(response); - else this.projectService.watchTypingLocations(response); + sendResponse(response: ts.server.SetTypings | ts.server.InvalidateCachedTypings | ts.server.BeginInstallTypes | ts.server.EndInstallTypes | ts.server.WatchTypingLocations | ts.server.PackageInstalledResponse) { + this.log.writeLine(`Sending response:${stringifyIndented(response)}`); + this.testTypingInstaller.onResponse(response); } enqueueInstallTypingsRequest(project: ts.server.Project, typeAcquisition: ts.TypeAcquisition, unresolvedImports: ts.SortedReadonlyArray) { - const request = ts.server.createInstallTypingsRequest(project, typeAcquisition, unresolvedImports, this.globalTypingsCacheLocation); + const request = ts.server.createInstallTypingsRequest( + project, + typeAcquisition, + unresolvedImports, + this.testTypingInstaller.globalTypingsCacheLocation, + ); this.install(request); } - - addPostExecAction(stdout: string | string[], requestId: number, packageNames: string[], cb: ts.server.typingsInstaller.RequestCompletedAction) { - const out = ts.isString(stdout) ? stdout : createNpmPackageJsonString(stdout); - const action: PostExecAction = { - success: !!out, - requestId, - packageNames, - callback: cb, - }; - this.postExecActions.push(action); - } } export interface TestTypingsInstallerOptions { + host: TestServerHost; + logger?: LoggerWithInMemoryLogs; globalTypingsCacheLocation?: string; throttleLimit?: number; installAction?: InstallAction; @@ -261,24 +201,39 @@ export interface TestTypingsInstallerOptions { export class TestTypingsInstaller implements ts.server.ITypingsInstaller { protected projectService!: ts.server.ProjectService; public installer!: TestTypingsInstallerWorker; + session!: TestSession; + packageInstalledPromise: { resolve(value: ts.ApplyCodeActionCommandResult): void; reject(reason: unknown): void; } | undefined; + + // Options readonly globalTypingsCacheLocation: string; - private readonly throttleLimit: number; - private installAction?: InstallAction; - private typesRegistry?: string | readonly string[]; + readonly throttleLimit: number; + readonly installAction: InstallAction; + readonly typesRegistry: string | readonly string[] | undefined; - constructor( - private installTypingHost: TestServerHost, - private logger: Logger, - options?: TestTypingsInstallerOptions, - ) { - this.globalTypingsCacheLocation = options?.globalTypingsCacheLocation || this.installTypingHost.getHostSpecificPath("/a/data"); - this.throttleLimit = options?.throttleLimit || 5; - this.installAction = options?.installAction; - this.typesRegistry = options?.typesRegistry; + constructor(options: TestTypingsInstallerOptions) { + this.globalTypingsCacheLocation = options.globalTypingsCacheLocation || options.host.getHostSpecificPath("/a/data"); + this.throttleLimit = options.throttleLimit || 5; + this.installAction = options.installAction !== undefined ? options.installAction : true; + this.typesRegistry = options.typesRegistry; } - isKnownTypesPackageName = ts.notImplemented; - installPackage = ts.notImplemented; + isKnownTypesPackageName(name: string): boolean { + // We want to avoid looking this up in the registry as that is expensive. So first check that it's actually an NPM package. + const validationResult = ts.JsTyping.validatePackageName(name); + if (validationResult !== ts.JsTyping.NameValidationResult.Ok) { + return false; + } + + return this.ensureInstaller().typesRegistry.has(name); + } + + installPackage(options: ts.server.InstallPackageOptionsWithProject): Promise { + this.ensureInstaller().installPackage({ kind: "installPackage", ...options }); + ts.Debug.assert(this.packageInstalledPromise === undefined); + return new Promise((resolve, reject) => { + this.packageInstalledPromise = { resolve, reject }; + }); + } attach(projectService: ts.server.ProjectService) { this.projectService = projectService; @@ -289,26 +244,65 @@ export class TestTypingsInstaller implements ts.server.ITypingsInstaller { } enqueueInstallTypingsRequest(project: ts.server.Project, typeAcquisition: ts.TypeAcquisition, unresolvedImports: ts.SortedReadonlyArray) { - if (!this.installer) { - this.installer = new TestTypingsInstallerWorker( - this.globalTypingsCacheLocation, - this.throttleLimit, - this.installTypingHost, - this.logger, - this.typesRegistry, - this.installAction, - ); - this.installer.attach(this.projectService); - } - this.installer.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports); + this.ensureInstaller().enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports); } -} -function createNpmPackageJsonString(installedTypings: string[]): string { - const dependencies: ts.MapLike = {}; - for (const typing of installedTypings) { - dependencies[typing] = "1.0.0"; + + private ensureInstaller() { + return this.installer ??= new TestTypingsInstallerWorker(this); + } + + onResponse(response: ts.server.SetTypings | ts.server.InvalidateCachedTypings | ts.server.BeginInstallTypes | ts.server.EndInstallTypes | ts.server.WatchTypingLocations | ts.server.PackageInstalledResponse) { + switch (response.kind) { + case ActionPackageInstalled: { + const { success, message } = response; + if (success) { + this.packageInstalledPromise!.resolve({ successMessage: message }); + } + else { + this.packageInstalledPromise!.reject(message); + } + this.packageInstalledPromise = undefined; + + this.projectService.updateTypingsForProject(response); + // The behavior is the same as for setTypings, so send the same event. + this.session.event(response, "setTypings"); + break; + } + case EventBeginInstallTypes: { + const body: ts.server.protocol.BeginInstallTypesEventBody = { + eventId: response.eventId, + packages: response.packagesToInstall, + }; + const eventName: ts.server.protocol.BeginInstallTypesEventName = "beginInstallTypes"; + this.session.event(body, eventName); + break; + } + case EventEndInstallTypes: { + const body: ts.server.protocol.EndInstallTypesEventBody = { + eventId: response.eventId, + packages: response.packagesToInstall, + success: response.installSuccess, + }; + const eventName: ts.server.protocol.EndInstallTypesEventName = "endInstallTypes"; + this.session.event(body, eventName); + break; + } + case ActionInvalidate: { + this.projectService.updateTypingsForProject(response); + break; + } + case ActionSet: { + this.projectService.updateTypingsForProject(response); + this.session.event(response, "setTypings"); + break; + } + case ActionWatchTypingLocations: + this.projectService.watchTypingLocations(response); + break; + default: + ts.assertType(response); + } } - return jsonToReadableText({ dependencies }); } function createTypesRegistryFileContent(list: readonly string[]): TypesRegistryFile { const versionMap = { diff --git a/src/testRunner/unittests/helpers/virtualFileSystemWithWatch.ts b/src/testRunner/unittests/helpers/virtualFileSystemWithWatch.ts index 3ec2eaac63b54..493ec2cd2aff9 100644 --- a/src/testRunner/unittests/helpers/virtualFileSystemWithWatch.ts +++ b/src/testRunner/unittests/helpers/virtualFileSystemWithWatch.ts @@ -2,6 +2,7 @@ import { createWatchUtils, } from "../../../harness/watchUtils"; import { + arrayFrom, clear, clone, combinePaths, @@ -42,6 +43,9 @@ import { sys, toPath, } from "../../_namespaces/ts"; +import { + typingsInstaller, +} from "../../_namespaces/ts.server"; import { timeIncrements, } from "../../_namespaces/vfs"; @@ -161,6 +165,12 @@ function invokeWatcherCallbacks(callbacks: readonly T[] | undefined, invokeCa } } +export interface StateLogger { + log(s: string): void; + logs: string[]; +} + +const exitMessage = "System Exit"; interface CallbackData { cb: TimeOutCallback; args: any[]; @@ -168,10 +178,13 @@ interface CallbackData { time: number; } class Callbacks { - readonly map: CallbackData[] = []; + readonly map = new Map(); private nextId = 1; + invoke: (invokeKey?: number) => void = invokeKey => this.invokeWorker(invokeKey); + private hasChanges = false; + private serializedKeys = new Map(); - constructor(private host: TestServerHost, readonly callbackType: string) { + constructor(private host: TestServerHost, readonly callbackType: string, private readonly swallowExitException?: boolean) { } getNextId() { @@ -181,27 +194,42 @@ class Callbacks { register(cb: TimeOutCallback, args: any[], ms?: number) { const timeoutId = this.nextId; this.nextId++; - this.map[timeoutId] = { cb, args, ms, time: this.host.getTime() }; + this.map.set(timeoutId, { cb, args, ms, time: this.host.getTime() }); + this.hasChanges = true; return timeoutId; } unregister(id: any) { if (typeof id === "number") { - delete this.map[id]; + this.hasChanges = this.map.delete(id) || this.hasChanges; } } - log() { + log(logChanges?: boolean) { const details: string[] = []; - for (const timeoutId in this.map) { - const { args } = this.map[Number(timeoutId)]; - details.push(`${timeoutId}: ${args[0]}`); + this.map.forEach(({ args }, timeoutId) => { + details.push(`${timeoutId}: ${args[0]}${!logChanges || this.serializedKeys.has(timeoutId) ? "" : " *new*"}`); + if (logChanges) this.serializedKeys.set(timeoutId, args[0]); + }); + const deleted: string[] = []; + if (logChanges && this.serializedKeys.size !== this.map.size) { + this.serializedKeys.forEach((value, key) => { + if (this.map.has(key)) return; + deleted.push(`${key}: ${value} *deleted*`); + this.serializedKeys.delete(key); + }); } - return `${this.callbackType} callback:: count: ${details.length}` + (details.length ? "\r\n" + details.join("\r\n") : ""); + return `${this.callbackType} callback:: count: ${this.map.size}` + + (deleted.length ? "\r\n" + deleted.join("\r\n") : "") + + (details.length ? "\r\n" + details.join("\r\n") : ""); } private invokeCallback(timeoutId: number) { - const { cb, args, ms, time } = this.map[timeoutId]; + const data = this.map.get(timeoutId); + if (!data) return; + const { cb, args, ms, time } = data; + this.map.delete(timeoutId); + this.serializedKeys.delete(timeoutId); if (ms !== undefined) { const newTime = ms + time; if (this.host.getTime() < newTime) { @@ -209,17 +237,43 @@ class Callbacks { } } cb(...args); - delete this.map[timeoutId]; } - invoke(invokeKey?: number) { - if (invokeKey) return this.invokeCallback(invokeKey); + invokeWorker(invokeKey?: number) { + try { + if (invokeKey) return this.invokeCallback(invokeKey); + + // Note: invoking a callback may result in new callbacks been queued, + // so do not clear the entire callback list regardless. Only remove the + // ones we have invoked. + const keys = arrayFrom(this.map.keys()); + for (const key of keys) { + this.invokeCallback(key); + } + } + catch (e) { + if (this.swallowExitException && e.message === exitMessage) { + return; + } + throw e; + } + } - // Note: invoking a callback may result in new callbacks been queued, - // so do not clear the entire callback list regardless. Only remove the - // ones we have invoked. - for (const key in this.map) { - this.invokeCallback(Number(key)); + switchToBaseliningInvoke(logger: StateLogger, serializeOutputOrder: SerializeOutputOrder) { + this.invoke = invokeKey => { + logger.log(`Before running ${this.log()}`); + this.host.serializeState(logger.logs, serializeOutputOrder); + if (invokeKey !== undefined) logger.log(`Invoking ${this.callbackType} callback:: timeoutId:: ${invokeKey}:: ${this.map.get(invokeKey)!.args[0]}`); + this.invokeWorker(invokeKey); + logger.log(`After running ${this.callbackType} callback:: count: ${this.map.size}`); + this.host.serializeState(logger.logs, serializeOutputOrder); + }; + } + + serialize(baseline: string[]) { + if (this.hasChanges) { + baseline.push(this.log(/*logChanges*/ true), ""); + this.hasChanges = false; } } } @@ -270,6 +324,18 @@ export interface TestServerHostOptions { environmentVariables?: Map; } +export type PendingInstallCallback = ( + pendingInstallInfo: string, + installedTypingsOrSuccess: string[] | string | boolean, + typingFiles: readonly File[], + onRequestCompleted: typingsInstaller.RequestCompletedAction, +) => void; + +export enum SerializeOutputOrder { + None, + BeforeDiff, + AfterDiff, +} export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost, ModuleResolutionHost { args: string[] = []; @@ -279,8 +345,9 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost, private time = timeIncrements; getCanonicalFileName: (s: string) => string; toPath: (f: string) => Path; - readonly timeoutCallbacks = new Callbacks(this, "Timeout"); + readonly timeoutCallbacks = new Callbacks(this, "Timeout", /*swallowExitException*/ true); readonly immediateCallbacks = new Callbacks(this, "Immedidate"); + readonly pendingInstalls = new Callbacks(this, "PendingInstalls"); readonly screenClears: number[] = []; readonly watchUtils = createWatchUtils("PolledWatches", "FsWatches"); @@ -861,15 +928,7 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost, } runQueuedTimeoutCallbacks(timeoutId?: number) { - try { - this.timeoutCallbacks.invoke(timeoutId); - } - catch (e) { - if (e.message === this.exitMessage) { - return; - } - throw e; - } + this.timeoutCallbacks.invoke(timeoutId); } runQueuedImmediateCallbacks() { @@ -884,6 +943,14 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost, this.immediateCallbacks.unregister(timeoutId); } + scheduleInstall(cb: TimeOutCallback, ...args: any[]) { + this.pendingInstalls.register(cb, args); + } + + runPendingInstalls() { + this.pendingInstalls.invoke(); + } + createDirectory(directoryName: string): void { const folder = this.toFsFolder(directoryName); @@ -945,6 +1012,7 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost, serializeOutput(baseline: string[]) { const output = this.getOutput(); + if (!this.output.length && !this.screenClears.length) return; let start = 0; baseline.push("Output::"); for (const screenClear of this.screenClears) { @@ -957,31 +1025,42 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost, this.clearOutput(); } - snap(): Map { - const result = new Map(); + private snap() { + this.serializedDiff = new Map(); this.fs.forEach((value, key) => { const cloneValue = clone(value); if (isFsFolder(cloneValue)) { cloneValue.entries = cloneValue.entries.map(clone) as SortedArray; } - result.set(key, cloneValue); + this.serializedDiff.set(key, cloneValue); }); + } - return result; + serializeState(baseline: string[], serializeOutput: SerializeOutputOrder) { + if (serializeOutput === SerializeOutputOrder.BeforeDiff) this.serializeOutput(baseline); + this.diff(baseline); + if (serializeOutput === SerializeOutputOrder.AfterDiff) this.serializeOutput(baseline); + this.serializeWatches(baseline); + this.timeoutCallbacks.serialize(baseline); + this.immediateCallbacks.serialize(baseline); + this.pendingInstalls.serialize(baseline); } writtenFiles?: Map; - diff(baseline: string[], base = new Map()) { + private serializedDiff = new Map(); + diff(baseline: string[]) { this.fs.forEach((newFsEntry, path) => { - diffFsEntry(baseline, base.get(path), newFsEntry, this.inodes?.get(path), this.writtenFiles); + diffFsEntry(baseline, this.serializedDiff.get(path), newFsEntry, this.inodes?.get(path), this.writtenFiles); }); - base.forEach((oldFsEntry, path) => { + this.serializedDiff.forEach((oldFsEntry, path) => { const newFsEntry = this.fs.get(path); if (!newFsEntry) { diffFsEntry(baseline, oldFsEntry, newFsEntry, this.inodes?.get(path), this.writtenFiles); } }); baseline.push(""); + this.snap(); + this.writtenFiles?.clear(); } serializeWatches(baseline?: string[]) { @@ -1006,14 +1085,13 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost, return fsEntry?.fullPath || realFullPath; } - readonly exitMessage = "System Exit"; exitCode: number | undefined; readonly resolvePath = (s: string) => s; readonly getExecutingFilePath = () => this.executingFilePath; readonly getCurrentDirectory = () => this.currentDirectory; exit(exitCode?: number) { this.exitCode = exitCode; - throw new Error(this.exitMessage); + throw new Error(exitMessage); } getEnvironmentVariable(name: string) { return this.environmentVariables && this.environmentVariables.get(name) || ""; diff --git a/src/testRunner/unittests/services/convertToAsyncFunction.ts b/src/testRunner/unittests/services/convertToAsyncFunction.ts index 657fd6a222727..3745e7e467472 100644 --- a/src/testRunner/unittests/services/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/services/convertToAsyncFunction.ts @@ -1,8 +1,5 @@ import * as Harness from "../../_namespaces/Harness"; import * as ts from "../../_namespaces/ts"; -import { - createProjectService, -} from "../helpers/tsserver"; import { createServerHost, File, @@ -11,6 +8,7 @@ import { extractTest, newLineCharacter, notImplementedHost, + TestProjectService, } from "./extract/helpers"; const libFile: File = { @@ -415,7 +413,7 @@ function testConvertToAsyncFunction(it: Mocha.PendingTestFunction, caption: stri files.push(moduleFile); } const host = createServerHost(files); - const projectService = createProjectService(host, { allowNonBaseliningLogger: true }); + const projectService = new TestProjectService(host); projectService.openClientFile(file.path); return ts.first(projectService.inferredProjects).getLanguageService(); } diff --git a/src/testRunner/unittests/services/extract/helpers.ts b/src/testRunner/unittests/services/extract/helpers.ts index 65995d34c5537..5f5c8b141973c 100644 --- a/src/testRunner/unittests/services/extract/helpers.ts +++ b/src/testRunner/unittests/services/extract/helpers.ts @@ -1,13 +1,44 @@ +import { + incrementalVerifier, +} from "../../../../harness/incrementalUtils"; +import { + createHasErrorMessageLogger, +} from "../../../../harness/tsserverLogger"; import * as Harness from "../../../_namespaces/Harness"; import * as ts from "../../../_namespaces/ts"; import { - createProjectService, -} from "../../helpers/tsserver"; + customTypesMap, +} from "../../helpers/typingsInstaller"; import { createServerHost, libFile, + TestServerHost, } from "../../helpers/virtualFileSystemWithWatch"; +export interface TestProjectServiceOptions extends ts.server.ProjectServiceOptions { + host: TestServerHost; +} +export type TestProjectServicePartialOptionsAndHost = Partial> & Pick; + +export class TestProjectService extends ts.server.ProjectService { + constructor(optsOrHost: TestServerHost | TestProjectServicePartialOptionsAndHost) { + // eslint-disable-next-line local/no-in-operator + const opts = "host" in optsOrHost ? + optsOrHost : + { host: optsOrHost }; + super({ + logger: createHasErrorMessageLogger(), + session: undefined, + cancellationToken: ts.server.nullCancellationToken, + useSingleInferredProject: false, + useInferredProjectPerProjectRoot: false, + typesMapLocation: customTypesMap.path, + incrementalVerifier, + ...opts, + }); + } +} + interface Range { pos: number; end: number; @@ -141,7 +172,7 @@ export function testExtractSymbol(caption: string, text: string, baselineFolder: function makeProgram(f: { path: string; content: string; }, includeLib?: boolean) { const host = createServerHost(includeLib ? [f, libFile] : [f]); // libFile is expensive to parse repeatedly - only test when required - const projectService = createProjectService(host, { allowNonBaseliningLogger: true }); + const projectService = new TestProjectService(host); projectService.openClientFile(f.path); const program = projectService.inferredProjects[0].getLanguageService().getProgram()!; const autoImportProvider = projectService.inferredProjects[0].getLanguageService().getAutoImportProvider(); @@ -166,7 +197,7 @@ export function testExtractSymbolFailed(caption: string, text: string, descripti content: t.source, }; const host = createServerHost([f, libFile]); - const projectService = createProjectService(host, { allowNonBaseliningLogger: true }); + const projectService = new TestProjectService(host); projectService.openClientFile(f.path); const program = projectService.inferredProjects[0].getLanguageService().getProgram()!; const sourceFile = program.getSourceFile(f.path)!; diff --git a/src/testRunner/unittests/services/organizeImports.ts b/src/testRunner/unittests/services/organizeImports.ts index ba87f5acba615..be6809a0ade23 100644 --- a/src/testRunner/unittests/services/organizeImports.ts +++ b/src/testRunner/unittests/services/organizeImports.ts @@ -1,14 +1,12 @@ import * as Harness from "../../_namespaces/Harness"; import * as ts from "../../_namespaces/ts"; -import { - createProjectService, -} from "../helpers/tsserver"; import { createServerHost, File, } from "../helpers/virtualFileSystemWithWatch"; import { newLineCharacter, + TestProjectService, } from "./extract/helpers"; describe("unittests:: services:: organizeImports", () => { @@ -985,7 +983,7 @@ export * from "lib"; function makeLanguageService(...files: File[]) { const host = createServerHost(files); - const projectService = createProjectService(host, { useSingleInferredProject: true, allowNonBaseliningLogger: true }); + const projectService = new TestProjectService({ host, useSingleInferredProject: true }); projectService.setCompilerOptionsForInferredProjects({ jsx: files.some(f => f.path.endsWith("x")) ? ts.JsxEmit.React : ts.JsxEmit.None }); files.forEach(f => projectService.openClientFile(f.path)); return projectService.inferredProjects[0].getLanguageService(); diff --git a/src/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index 86ddc0b5d971c..788294315e5b9 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -34,7 +34,7 @@ import { import { changeToHostTrackingWrittenFiles, libFile, - TestServerHost, + SerializeOutputOrder, } from "../helpers/virtualFileSystemWithWatch"; describe("unittests:: tsbuild:: on 'sample1' project", () => { @@ -317,7 +317,6 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => { it("building using getNextInvalidatedProject", () => { const baseline: string[] = []; - let oldSnap: ReturnType | undefined; const system = changeToHostTrackingWrittenFiles( fakes.patchHostForBuildInfoReadWrite( getSysForSampleProjectReferences(), @@ -327,7 +326,7 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => { const host = createSolutionBuilderHostForBaseline(system); const builder = ts.createSolutionBuilder(host, ["tests"], {}); baseline.push("Input::"); - baselineState(); + system.serializeState(baseline, SerializeOutputOrder.BeforeDiff); verifyBuildNextResult(); // core verifyBuildNextResult(); // logic verifyBuildNextResult(); // tests @@ -338,14 +337,7 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => { const project = builder.getNextInvalidatedProject(); const result = project && project.done(); baseline.push(`Project Result:: ${jsonToReadableText({ project: project?.project, result })}`); - baselineState(); - } - - function baselineState() { - system.serializeOutput(baseline); - system.diff(baseline, oldSnap); - system.writtenFiles.clear(); - oldSnap = system.snap(); + system.serializeState(baseline, SerializeOutputOrder.BeforeDiff); } }); @@ -376,7 +368,6 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => { describe("project invalidation", () => { it("invalidates projects correctly", () => { const baseline: string[] = []; - let oldSnap: ReturnType | undefined; const system = changeToHostTrackingWrittenFiles( fakes.patchHostForBuildInfoReadWrite( getSysForSampleProjectReferences(), @@ -414,10 +405,7 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => { function baselineState(heading: string) { baseline.push(heading); - system.serializeOutput(baseline); - system.diff(baseline, oldSnap); - system.writtenFiles.clear(); - oldSnap = system.snap(); + system.serializeState(baseline, SerializeOutputOrder.BeforeDiff); } }); }); diff --git a/src/testRunner/unittests/tsbuildWatch/programUpdates.ts b/src/testRunner/unittests/tsbuildWatch/programUpdates.ts index e482c646ccac8..814b53817e4db 100644 --- a/src/testRunner/unittests/tsbuildWatch/programUpdates.ts +++ b/src/testRunner/unittests/tsbuildWatch/programUpdates.ts @@ -35,7 +35,7 @@ describe("unittests:: tsbuildWatch:: watchMode:: program updates", () => { }); it("verify building references watches only those projects", () => { - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(getSysForSampleProjectReferences()); + const { sys, baseline, cb, getPrograms } = createBaseline(getSysForSampleProjectReferences()); const host = createSolutionBuilderWithWatchHostForBaseline(sys, cb); const solutionBuilder = ts.createSolutionBuilderWithWatch(host, ["tests"], { watch: true }); solutionBuilder.buildReferences("tests"); @@ -45,7 +45,6 @@ describe("unittests:: tsbuildWatch:: watchMode:: program updates", () => { commandLineArgs: ["--b", "--w"], sys, baseline, - oldSnap, getPrograms, watchOrSolution: solutionBuilder, }); diff --git a/src/testRunner/unittests/tsbuildWatch/publicApi.ts b/src/testRunner/unittests/tsbuildWatch/publicApi.ts index c4fa1b747343e..487b160a8f772 100644 --- a/src/testRunner/unittests/tsbuildWatch/publicApi.ts +++ b/src/testRunner/unittests/tsbuildWatch/publicApi.ts @@ -54,7 +54,7 @@ export enum e2 { } export function f22() { } // trailing`, }; const commandLineArgs = ["--b", "--w"]; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([libFile, solution, sharedConfig, sharedIndex, webpackConfig, webpackIndex], { currentDirectory: "/user/username/projects/myproject" })); + const { sys, baseline, cb, getPrograms } = createBaseline(createWatchedSystem([libFile, solution, sharedConfig, sharedIndex, webpackConfig, webpackIndex], { currentDirectory: "/user/username/projects/myproject" })); const buildHost = createSolutionBuilderWithWatchHostForBaseline(sys, cb); buildHost.getCustomTransformers = getCustomTransformers; const builder = ts.createSolutionBuilderWithWatch(buildHost, [solution.path], { verbose: true }); @@ -65,7 +65,6 @@ export function f22() { } // trailing`, commandLineArgs, sys, baseline, - oldSnap, getPrograms, edits: [ { diff --git a/src/testRunner/unittests/tsbuildWatch/watchEnvironment.ts b/src/testRunner/unittests/tsbuildWatch/watchEnvironment.ts index e17f46ade75f5..a8f730e192267 100644 --- a/src/testRunner/unittests/tsbuildWatch/watchEnvironment.ts +++ b/src/testRunner/unittests/tsbuildWatch/watchEnvironment.ts @@ -27,7 +27,7 @@ describe("unittests:: tsbuildWatch:: watchEnvironment:: tsbuild:: watchMode:: wi const allPkgFiles = pkgs(pkgFiles); const system = createWatchedSystem([libFile, typing, ...flatArray(allPkgFiles)], { currentDirectory: project }); writePkgReferences(system); - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(system); + const { sys, baseline, cb, getPrograms } = createBaseline(system); const host = createSolutionBuilderWithWatchHostForBaseline(sys, cb); const solutionBuilder = ts.createSolutionBuilderWithWatch(host, ["tsconfig.json"], { watch: true, verbose: true }); solutionBuilder.build(); @@ -37,7 +37,6 @@ describe("unittests:: tsbuildWatch:: watchEnvironment:: tsbuild:: watchMode:: wi commandLineArgs: ["--b", "--w"], sys, baseline, - oldSnap, getPrograms, edits: [ { @@ -77,7 +76,7 @@ describe("unittests:: tsbuildWatch:: watchEnvironment:: tsbuild:: watchMode:: wi { caption: "modify typing file", edit: sys => sys.writeFile(typing.path, `${typing.content}export const typing1 = 10;`), - timeouts: sys => sys.logTimeoutQueueLength(), + timeouts: ts.noop, }, ], watchOrSolution: solutionBuilder, diff --git a/src/testRunner/unittests/tsc/cancellationToken.ts b/src/testRunner/unittests/tsc/cancellationToken.ts index c710adf9f7224..381bef09b273a 100644 --- a/src/testRunner/unittests/tsc/cancellationToken.ts +++ b/src/testRunner/unittests/tsc/cancellationToken.ts @@ -55,7 +55,7 @@ describe("unittests:: tsc:: builder cancellationToken", () => { path: `/user/username/projects/myproject/tsconfig.json`, content: jsonToReadableText({ compilerOptions: { incremental: true, declaration: true } }), }; - const { sys, baseline, oldSnap: originalSnap } = createBaseline(createWatchedSystem( + const { sys, baseline } = createBaseline(createWatchedSystem( [aFile, bFile, cFile, dFile, config, libFile], { currentDirectory: "/user/username/projects/myproject" }, )); @@ -73,7 +73,6 @@ describe("unittests:: tsc:: builder cancellationToken", () => { let programs: CommandLineProgram[] = ts.emptyArray; let oldPrograms: CommandLineProgram[] = ts.emptyArray; let builderProgram: ts.EmitAndSemanticDiagnosticsBuilderProgram = undefined!; - let oldSnap = originalSnap; let cancel = false; const cancellationToken: ts.CancellationToken = { isCancellationRequested: () => cancel, @@ -90,7 +89,7 @@ describe("unittests:: tsc:: builder cancellationToken", () => { // Cancel on first semantic operation // Change - oldSnap = applyEdit( + applyEdit( sys, baseline, sys => sys.appendFile(cFile.path, "export function foo() {}"), @@ -114,7 +113,6 @@ describe("unittests:: tsc:: builder cancellationToken", () => { getPrograms: () => programs, oldPrograms, sys, - oldSnap, }); // Normal emit again @@ -128,7 +126,7 @@ describe("unittests:: tsc:: builder cancellationToken", () => { Harness.Baseline.runBaseline(`tsc/cancellationToken/${scenario.split(" ").join("-")}.js`, baseline.join("\r\n")); function noChange(caption: string) { - oldSnap = applyEdit(sys, baseline, ts.noop, caption); + applyEdit(sys, baseline, ts.noop, caption); } function updatePrograms() { @@ -162,7 +160,6 @@ describe("unittests:: tsc:: builder cancellationToken", () => { getPrograms: () => programs, oldPrograms, sys, - oldSnap, }); } diff --git a/src/testRunner/unittests/tscWatch/incremental.ts b/src/testRunner/unittests/tscWatch/incremental.ts index a2b098774fead..04fd08d6ef24b 100644 --- a/src/testRunner/unittests/tscWatch/incremental.ts +++ b/src/testRunner/unittests/tscWatch/incremental.ts @@ -12,7 +12,6 @@ import { import { applyEdit, createBaseline, - SystemSnap, verifyTscWatch, watchBaseline, } from "../helpers/tscWatch"; @@ -52,21 +51,21 @@ describe("unittests:: tsc-watch:: emit file --incremental", () => { { subScenario, files, optionsToExtend, modifyFs }: VerifyIncrementalWatchEmitInput, incremental: boolean, ) { - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem(files(), { currentDirectory: project })); + const { sys, baseline, cb, getPrograms } = createBaseline(createWatchedSystem(files(), { currentDirectory: project })); if (incremental) sys.exit = exitCode => sys.exitCode = exitCode; const argsToPass = [incremental ? "-i" : "-w", ...(optionsToExtend || ts.emptyArray)]; baseline.push(`${sys.getExecutingFilePath()} ${argsToPass.join(" ")}`); let oldPrograms: readonly CommandLineProgram[] = ts.emptyArray; - build(oldSnap); + build(); if (modifyFs) { - const oldSnap = applyEdit(sys, baseline, modifyFs); - build(oldSnap); + applyEdit(sys, baseline, modifyFs); + build(); } Harness.Baseline.runBaseline(`${ts.isBuild(argsToPass) ? "tsbuild/watchMode" : "tscWatch"}/incremental/${subScenario.split(" ").join("-")}-${incremental ? "incremental" : "watch"}.js`, baseline.join("\r\n")); - function build(oldSnap: SystemSnap) { + function build() { const closer = ts.executeCommandLine( sys, cb, @@ -77,7 +76,6 @@ describe("unittests:: tsc-watch:: emit file --incremental", () => { getPrograms, oldPrograms, sys, - oldSnap, }); if (closer) closer.close(); } diff --git a/src/testRunner/unittests/tscWatch/programUpdates.ts b/src/testRunner/unittests/tscWatch/programUpdates.ts index 4b07b7d25ad43..73268c59db8eb 100644 --- a/src/testRunner/unittests/tscWatch/programUpdates.ts +++ b/src/testRunner/unittests/tscWatch/programUpdates.ts @@ -625,7 +625,7 @@ export class A { path: "/a/d/f3.ts", content: "export let y = 1;", }; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([libFile, file1, file2, file3])); + const { sys, baseline, cb, getPrograms } = createBaseline(createWatchedSystem([libFile, file1, file2, file3])); const host = createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ rootFiles: [file2.path, file3.path], system: sys, @@ -640,11 +640,9 @@ export class A { getPrograms, oldPrograms: ts.emptyArray, sys, - oldSnap, }); const { cb: cb2, getPrograms: getPrograms2 } = commandLineCallbacks(sys); - const oldSnap2 = sys.snap(); baseline.push("createing separate watcher"); ts.createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ rootFiles: [file1.path], @@ -658,10 +656,8 @@ export class A { getPrograms: getPrograms2, oldPrograms: ts.emptyArray, sys, - oldSnap: oldSnap2, }); - sys.logTimeoutQueueLength(); baseline.push(`First program is not updated:: ${getPrograms() === ts.emptyArray}`); baseline.push(`Second program is not updated:: ${getPrograms2() === ts.emptyArray}`); Harness.Baseline.runBaseline(`tscWatch/${scenario}/two-watch-programs-are-not-affected-by-each-other.js`, baseline.join("\r\n")); @@ -1205,7 +1201,7 @@ declare const eval: any`, path: "/a/compile", content: "let x = 1", }; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([f, libFile])); + const { sys, baseline, cb, getPrograms } = createBaseline(createWatchedSystem([f, libFile])); const watch = ts.createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ rootFiles: [f.path], system: sys, @@ -1219,7 +1215,6 @@ declare const eval: any`, commandLineArgs: ["--w", f.path], sys, baseline, - oldSnap, getPrograms, watchOrSolution: watch, }); @@ -2087,7 +2082,7 @@ import { x } from "../b";`, { caption: "Add excluded file to project1", edit: sys => sys.ensureFileOrFolder({ path: `/user/username/projects/myproject/projects/project1/temp/file.d.ts`, content: `declare class file {}` }), - timeouts: sys => sys.logTimeoutQueueLength(), + timeouts: ts.noop, }, { caption: "Delete output of class3", diff --git a/src/testRunner/unittests/tscWatch/projectsWithReferences.ts b/src/testRunner/unittests/tscWatch/projectsWithReferences.ts index 36a8ed801f271..333a0629dadca 100644 --- a/src/testRunner/unittests/tscWatch/projectsWithReferences.ts +++ b/src/testRunner/unittests/tscWatch/projectsWithReferences.ts @@ -1,3 +1,6 @@ +import { + noop, +} from "../../_namespaces/ts"; import { jsonToReadableText, } from "../helpers"; @@ -43,7 +46,7 @@ describe("unittests:: tsc-watch:: projects with references: invoking when refere }, // not ideal, but currently because of d.ts but no new file is written // There will be timeout queued even though file contents are same - timeouts: sys => sys.logTimeoutQueueLength(), + timeouts: noop, }, { caption: "non local edit in logic ts, and build logic", diff --git a/src/testRunner/unittests/tscWatch/resolutionCache.ts b/src/testRunner/unittests/tscWatch/resolutionCache.ts index 83a39a38f3af0..36fb5dbede741 100644 --- a/src/testRunner/unittests/tscWatch/resolutionCache.ts +++ b/src/testRunner/unittests/tscWatch/resolutionCache.ts @@ -31,7 +31,7 @@ describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution content: `foo()`, }; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([root, imported, libFile])); + const { sys, baseline, cb, getPrograms } = createBaseline(createWatchedSystem([root, imported, libFile])); const host = createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ rootFiles: [root.path], system: sys, @@ -48,7 +48,6 @@ describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution commandLineArgs: ["--w", root.path], sys, baseline, - oldSnap, getPrograms, edits: [ { @@ -117,7 +116,7 @@ describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution content: `export const y = 1;`, }; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([root, libFile])); + const { sys, baseline, cb, getPrograms } = createBaseline(createWatchedSystem([root, libFile])); const host = createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ rootFiles: [root.path], system: sys, @@ -146,7 +145,6 @@ describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution commandLineArgs: ["--w", root.path], sys, baseline, - oldSnap, getPrograms, edits: [{ caption: "write imported file", @@ -175,7 +173,7 @@ describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution content: `export const y = 1;export const x = 10;`, }; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([root, imported, libFile])); + const { sys, baseline, cb, getPrograms } = createBaseline(createWatchedSystem([root, imported, libFile])); const host = createWatchCompilerHostOfFilesAndCompilerOptionsForBaseline({ rootFiles: [root.path], system: sys, @@ -202,7 +200,6 @@ describe("unittests:: tsc-watch:: resolutionCache:: tsc-watch module resolution commandLineArgs: ["--w", root.path], sys, baseline, - oldSnap, getPrograms, edits: [ { @@ -411,7 +408,7 @@ declare module "fs" { path: `/user/username/projects/myproject/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`, content: jsonToReadableText({ something: 10 }), }), - timeouts: sys => sys.logTimeoutQueueLength(), + timeouts: ts.noop, }, ], }); @@ -467,8 +464,7 @@ declare namespace myapp { { caption: "No change, just check program", edit: ts.noop, - timeouts: (sys, [[oldProgram, oldBuilderProgram]], watchorSolution) => { - sys.logTimeoutQueueLength(); + timeouts: (_sys, [[oldProgram, oldBuilderProgram]], watchorSolution) => { const newProgram = (watchorSolution as ts.WatchOfConfigFile).getProgram(); assert.strictEqual(newProgram, oldBuilderProgram, "No change so builder program should be same"); assert.strictEqual(newProgram.getProgram(), oldProgram, "No change so program should be same"); diff --git a/src/testRunner/unittests/tscWatch/sourceOfProjectReferenceRedirect.ts b/src/testRunner/unittests/tscWatch/sourceOfProjectReferenceRedirect.ts index 146ba559d1aec..c02a60e708d64 100644 --- a/src/testRunner/unittests/tscWatch/sourceOfProjectReferenceRedirect.ts +++ b/src/testRunner/unittests/tscWatch/sourceOfProjectReferenceRedirect.ts @@ -32,7 +32,7 @@ describe("unittests:: tsc-watch:: watchAPI:: with sourceOfProjectReferenceRedire } function verifyWatch({ files, config, subScenario }: VerifyWatchInput, alreadyBuilt: boolean) { - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline( + const { sys, baseline, cb, getPrograms } = createBaseline( createWatchedSystem(files), alreadyBuilt ? (sys, originalRead) => { solutionBuildWithBaseline(sys, [config], originalRead); @@ -52,7 +52,6 @@ describe("unittests:: tsc-watch:: watchAPI:: with sourceOfProjectReferenceRedire commandLineArgs: ["--w", "--p", config], sys, baseline, - oldSnap, getPrograms, watchOrSolution: watch, useSourceOfProjectReferenceRedirect: ts.returnTrue, diff --git a/src/testRunner/unittests/tscWatch/watchApi.ts b/src/testRunner/unittests/tscWatch/watchApi.ts index b7a193e69adeb..1f8e6077b6f40 100644 --- a/src/testRunner/unittests/tscWatch/watchApi.ts +++ b/src/testRunner/unittests/tscWatch/watchApi.ts @@ -45,7 +45,7 @@ describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolu path: `/user/username/projects/myproject/settings.json`, content: jsonToReadableText({ content: "Print this" }), }; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem( + const { sys, baseline, cb, getPrograms } = createBaseline(createWatchedSystem( [libFile, mainFile, config, settingsJson], { currentDirectory: "/user/username/projects/myproject" }, )); @@ -72,7 +72,6 @@ describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolu commandLineArgs: ["--w", "--p", config.path], sys, baseline, - oldSnap, getPrograms, watchOrSolution: watch, }); @@ -81,7 +80,7 @@ describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolu describe("hasInvalidatedResolutions", () => { function verifyWatch(subScenario: string, implementHasInvalidatedResolution: boolean) { it(subScenario, () => { - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem({ + const { sys, baseline, cb, getPrograms } = createBaseline(createWatchedSystem({ [`/user/username/projects/myproject/tsconfig.json`]: jsonToReadableText({ compilerOptions: { traceResolution: true, extendedDiagnostics: true }, files: ["main.ts"], @@ -105,7 +104,6 @@ describe("unittests:: tsc-watch:: watchAPI:: tsc-watch with custom module resolu commandLineArgs: ["--w"], sys, baseline, - oldSnap, getPrograms, edits: [ { @@ -149,7 +147,7 @@ describe("unittests:: tsc-watch:: watchAPI:: tsc-watch expose error count to wat path: `/user/username/projects/myproject/index.ts`, content: "let compiler = new Compiler(); for (let i = 0; j < 5; i++) {}", }; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem( + const { sys, baseline, cb, getPrograms } = createBaseline(createWatchedSystem( [libFile, mainFile, config], { currentDirectory: "/user/username/projects/myproject" }, )); @@ -172,7 +170,6 @@ describe("unittests:: tsc-watch:: watchAPI:: tsc-watch expose error count to wat commandLineArgs: ["--w", "--p", config.path], sys, baseline, - oldSnap, getPrograms, watchOrSolution: watch, }); @@ -189,7 +186,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost does not implement s path: `/user/username/projects/myproject/main.ts`, content: "const x = 10;", }; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(createWatchedSystem([config, mainFile, libFile])); + const { sys, baseline, cb, getPrograms } = createBaseline(createWatchedSystem([config, mainFile, libFile])); const host = createWatchCompilerHostOfConfigFileForBaseline({ configFileName: config.path, system: sys, @@ -204,13 +201,11 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost does not implement s commandLineArgs: ["--w", "--p", config.path], sys, baseline, - oldSnap, getPrograms, edits: [{ caption: "Write a file", edit: sys => sys.writeFile(`/user/username/projects/myproject/bar.ts`, "const y =10;"), - timeouts: sys => { - sys.logTimeoutQueueLength(); + timeouts: () => { watch.getProgram(); }, }], @@ -233,7 +228,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost can add extraFileExt path: `/user/username/projects/myproject/other.vue`, content: "", }; - const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline( + const { sys, baseline, cb, getPrograms } = createBaseline( createWatchedSystem([config, mainFile, otherFile, libFile]), ); const host = createWatchCompilerHostOfConfigFileForBaseline({ @@ -250,7 +245,6 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost can add extraFileExt commandLineArgs: ["--w", "--p", config.path], sys, baseline, - oldSnap, getPrograms, edits: [{ caption: "Write a file", @@ -293,7 +287,6 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticD ) { const { cb, getPrograms } = commandLineCallbacks(sys); baseline.push(`tsc --w${optionsToExtend?.noEmit ? " --noEmit" : ""}`); - const oldSnap = sys.snap(); const host = createWatchCompilerHostOfConfigFileForBaseline({ configFileName: config.path, optionsToExtend, @@ -307,7 +300,6 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticD getPrograms, oldPrograms: ts.emptyArray, sys, - oldSnap, }); watch.close(); } @@ -354,7 +346,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticD } it("verifies that noEmit is handled on createSemanticDiagnosticsBuilderProgram and typechecking happens only on affected files", () => { - const { sys, baseline, oldSnap, cb, getPrograms, config, mainFile } = createSystem("{}", "export const x = 10;"); + const { sys, baseline, cb, getPrograms, config, mainFile } = createSystem("{}", "export const x = 10;"); const host = createWatchCompilerHostOfConfigFileForBaseline({ configFileName: config.path, optionsToExtend: { noEmit: true }, @@ -369,7 +361,6 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticD commandLineArgs: ["--w", "--p", config.path], sys, baseline, - oldSnap, getPrograms, edits: [{ caption: "Modify a file", @@ -469,7 +460,6 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticD applyEdit(sys, baseline, sys => sys.writeFile(mainFile.path, "export const x = 10;"), "Fix error"); const { cb, getPrograms } = commandLineCallbacks(sys); - const oldSnap = sys.snap(); const reportDiagnostic = ts.createDiagnosticReporter(sys, /*pretty*/ true); const reportWatchStatus = ts.createWatchStatusReporter(sys, /*pretty*/ true); const host = ts.createWatchCompilerHostOfConfigFile({ @@ -498,7 +488,6 @@ describe("unittests:: tsc-watch:: watchAPI:: when watchHost uses createSemanticD getPrograms, oldPrograms: ts.emptyArray, sys, - oldSnap, }); Harness.Baseline.runBaseline(`tscWatch/watchApi/semantic-builder-emitOnlyDts.js`, baseline.join("\r\n")); }); @@ -585,12 +574,12 @@ describe("unittests:: tsc-watch:: watchAPI:: when getParsedCommandLine is implem { caption: "Add excluded file to project1", edit: sys => sys.ensureFileOrFolder({ path: `/user/username/projects/myproject/projects/project1/temp/file.d.ts`, content: `declare class file {}` }), - timeouts: sys => sys.logTimeoutQueueLength(), + timeouts: ts.noop, }, { caption: "Add output of class3", edit: sys => sys.writeFile(`/user/username/projects/myproject/projects/project1/class3.d.ts`, `declare class class3 {}`), - timeouts: sys => sys.logTimeoutQueueLength(), + timeouts: ts.noop, }, ], watchOrSolution: watch, @@ -622,7 +611,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when getParsedCommandLine is implem { caption: "Add excluded file to project1", edit: sys => sys.ensureFileOrFolder({ path: `/user/username/projects/myproject/projects/project1/temp/file.d.ts`, content: `declare class file {}` }), - timeouts: sys => sys.logTimeoutQueueLength(), + timeouts: ts.noop, }, { caption: "Delete output of class3", @@ -684,7 +673,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when builder emit occurs with emitO program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ true); baseline.cb(program); }, - timeouts: sys => sys.logTimeoutQueueLength(), + timeouts: ts.noop, }, { caption: "Emit all files", @@ -693,7 +682,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when builder emit occurs with emitO program.emit(); baseline.cb(program); }, - timeouts: sys => sys.logTimeoutQueueLength(), + timeouts: ts.noop, }, { caption: "Emit with emitOnlyDts shouldnt emit anything", @@ -702,7 +691,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when builder emit occurs with emitO program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ true); baseline.cb(program); }, - timeouts: sys => sys.logTimeoutQueueLength(), + timeouts: ts.noop, }, { caption: "Emit full should not emit anything", @@ -711,7 +700,7 @@ describe("unittests:: tsc-watch:: watchAPI:: when builder emit occurs with emitO program.emit(); baseline.cb(program); }, - timeouts: sys => sys.logTimeoutQueueLength(), + timeouts: ts.noop, }, ], watchOrSolution: watch, diff --git a/src/testRunner/unittests/tscWatch/watchEnvironment.ts b/src/testRunner/unittests/tscWatch/watchEnvironment.ts index 9f8974d6313ab..1bb40df865dd2 100644 --- a/src/testRunner/unittests/tscWatch/watchEnvironment.ts +++ b/src/testRunner/unittests/tscWatch/watchEnvironment.ts @@ -282,17 +282,17 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po caption: "Start npm install", // npm install edit: sys => sys.createDirectory(`/user/username/projects/myproject/node_modules`), - timeouts: sys => sys.logTimeoutQueueLength(), // To update folder structure + timeouts: ts.noop, // To update folder structure }, { caption: "npm install folder creation of file2", edit: sys => sys.createDirectory(`/user/username/projects/myproject/node_modules/file2`), - timeouts: sys => sys.logTimeoutQueueLength(), // To update folder structure + timeouts: ts.noop, // To update folder structure }, { caption: "npm install index file in file2", edit: sys => sys.writeFile(`/user/username/projects/myproject/node_modules/file2/index.d.ts`, `export const x = 10;`), - timeouts: sys => sys.logTimeoutQueueLength(), // To update folder structure + timeouts: ts.noop, // To update folder structure }, { caption: "Updates the program", @@ -503,7 +503,7 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po { caption: "Change foo", edit: sys => sys.replaceFileText(`/user/username/projects/myproject/node_modules/bar/foo.d.ts`, "foo", "fooBar"), - timeouts: sys => sys.logTimeoutQueueLength(), + timeouts: ts.noop, }, ], }); @@ -517,7 +517,7 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po { caption: "delete fooBar", edit: sys => sys.deleteFile(`/user/username/projects/myproject/node_modules/bar/fooBar.d.ts`), - timeouts: sys => sys.logTimeoutQueueLength(), + timeouts: ts.noop, }, ], }); @@ -536,7 +536,7 @@ describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different po { caption: "add new folder to temp", edit: sys => sys.ensureFileOrFolder({ path: `/user/username/projects/myproject/node_modules/bar/temp/fooBar/index.d.ts`, content: "export function temp(): string;" }), - timeouts: sys => sys.logTimeoutQueueLength(), + timeouts: ts.noop, }, ], }); diff --git a/src/testRunner/unittests/tsserver/applyChangesToOpenFiles.ts b/src/testRunner/unittests/tsserver/applyChangesToOpenFiles.ts index 06cd1ed05c801..aa164b8a68e41 100644 --- a/src/testRunner/unittests/tsserver/applyChangesToOpenFiles.ts +++ b/src/testRunner/unittests/tsserver/applyChangesToOpenFiles.ts @@ -1,6 +1,3 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { commonFile1, @@ -8,7 +5,7 @@ import { } from "../helpers/tscWatch"; import { baselineTsserverLogs, - createSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -36,7 +33,7 @@ ${file.content}`; content: "let z = 1;", }; const host = createServerHost([app, file3, commonFile1, commonFile2, libFile, configFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.Open, arguments: { file: app.path }, diff --git a/src/testRunner/unittests/tsserver/autoImportProvider.ts b/src/testRunner/unittests/tsserver/autoImportProvider.ts index 00aee3dda1b96..5ccd8b060166a 100644 --- a/src/testRunner/unittests/tsserver/autoImportProvider.ts +++ b/src/testRunner/unittests/tsserver/autoImportProvider.ts @@ -1,14 +1,11 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { jsonToReadableText, } from "../helpers"; import { baselineTsserverLogs, - createSession, openFilesForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -374,7 +371,7 @@ describe("unittests:: tsserver:: autoImportProvider - monorepo", () => { function setup(files: File[]) { const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); const projectService = session.getProjectService(); return { host, diff --git a/src/testRunner/unittests/tsserver/auxiliaryProject.ts b/src/testRunner/unittests/tsserver/auxiliaryProject.ts index 4b28ce4051b54..cc14c79aeff2e 100644 --- a/src/testRunner/unittests/tsserver/auxiliaryProject.ts +++ b/src/testRunner/unittests/tsserver/auxiliaryProject.ts @@ -1,6 +1,3 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { dedent, @@ -10,9 +7,9 @@ import { } from "../helpers"; import { baselineTsserverLogs, - createSession, openFilesForSession, protocolFileLocationFromSubstring, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -35,12 +32,11 @@ describe("unittests:: tsserver:: auxiliaryProject::", () => { content: `export class B {}`, }; const host = createServerHost([aTs, bDts, bJs]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - const projectService = session.getProjectService(); + const session = new TestSession(host); openFilesForSession([aTs], session); // Open file is in inferred project - const inferredProject = projectService.inferredProjects[0]; + const inferredProject = session.getProjectService().inferredProjects[0]; // getNoDtsResolutionProject will create an AuxiliaryProject with a.ts and b.js session.executeCommandSeq({ @@ -53,7 +49,7 @@ describe("unittests:: tsserver:: auxiliaryProject::", () => { // The AuxiliaryProject should never be the default project for anything, so // the ScriptInfo should still report being an orphan, and getting its default // project should throw. - const bJsScriptInfo = ts.Debug.checkDefined(projectService.getScriptInfo(bJs.path)); + const bJsScriptInfo = ts.Debug.checkDefined(session.getProjectService().getScriptInfo(bJs.path)); assert(bJsScriptInfo.isOrphan()); assert(bJsScriptInfo.isContainedByBackgroundProject()); assert.deepEqual(bJsScriptInfo.containingProjects, [auxProject]); @@ -106,7 +102,7 @@ describe("unittests:: tsserver:: auxiliaryProject::", () => { [indexFile.path]: indexFile.content, [libFile.path]: libFile.content, }); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([indexFile], session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.FindSourceDefinition, @@ -165,7 +161,7 @@ describe("unittests:: tsserver:: auxiliaryProject::", () => { [indexFile.path]: indexFile.content, [libFile.path]: libFile.content, }); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([indexFile], session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.FindSourceDefinition, diff --git a/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts b/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts index 0456ac8c0d5bc..184da488e05c7 100644 --- a/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts +++ b/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts @@ -2,8 +2,7 @@ import { IncrementalVerifierCallbacks, } from "../../../harness/incrementalUtils"; import { - createLoggerWithInMemoryLogs, - Logger, + LoggerWithInMemoryLogs, } from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { @@ -11,11 +10,10 @@ import { } from "../helpers"; import { baselineTsserverLogs, - createProjectService, - createSession, logDiagnostics, openFilesForSession, - TestProjectService, + setCompilerOptionsForInferredProjectsRequestForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -79,13 +77,13 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS return calledMap; } - function logCacheEntry(logger: Logger, callback: CalledMaps) { + function logCacheEntry(logger: LoggerWithInMemoryLogs, callback: CalledMaps) { const result = Array.from<[string, (true | CalledWithFiveArgs)[]], { key: string; count: number; }>(calledMaps[callback].entries(), ([key, arr]) => ({ key, count: arr.length })); logger.info(`${callback}:: ${jsonToReadableText(result)}`); calledMaps[callback].clear(); } - function logCacheAndClear(logger: Logger) { + function logCacheAndClear(logger: LoggerWithInMemoryLogs) { forEachHostProperty(prop => logCacheEntry(logger, prop)); } @@ -98,9 +96,9 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS } } - function logSemanticDiagnostics(projectService: TestProjectService, project: ts.server.Project, file: File) { + function logSemanticDiagnostics(session: TestSession, project: ts.server.Project, file: File) { logDiagnostics( - projectService, + session, `getSemanticDiagnostics:: ${file.path}`, project, project.getLanguageService().getSemanticDiagnostics(file.path), @@ -120,15 +118,18 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS }; const host = createServerHost([root, imported]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.setCompilerOptionsForInferredProjects({ module: ts.ModuleKind.AMD, noLib: true }); - projectService.openClientFile(root.path); - const project = projectService.inferredProjects[0]; + const session = new TestSession(host); + setCompilerOptionsForInferredProjectsRequestForSession({ + module: ts.server.protocol.ModuleKind.AMD, + noLib: true, + }, session); + openFilesForSession([root], session); + const project = session.getProjectService().inferredProjects[0]; const rootScriptInfo = project.getRootScriptInfos()[0]; assert.equal(rootScriptInfo.fileName, root.path); // ensure that imported file was found - logSemanticDiagnostics(projectService, project, imported); + logSemanticDiagnostics(session, project, imported); const logCacheAndClear = createLoggerTrackingHostCalls(host); @@ -136,29 +137,33 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS // ensure file has correct number of errors after edit editContent(`import {x} from "f1"; var x: string = 1;`); - logSemanticDiagnostics(projectService, project, imported); - logCacheAndClear(projectService.logger); + logSemanticDiagnostics(session, project, imported); + logCacheAndClear(session.logger); // trigger synchronization to make sure that the host will try to find 'f2' module on disk editContent(`import {x} from "f2"`); try { // trigger synchronization to make sure that the host will try to find 'f2' module on disk - logSemanticDiagnostics(projectService, project, imported); + logSemanticDiagnostics(session, project, imported); } catch (e) { - projectService.logger.info(e.message); + session.logger.info(e.message); } - logCacheAndClear(projectService.logger); + logCacheAndClear(session.logger); editContent(`import {x} from "f1"`); - logSemanticDiagnostics(projectService, project, imported); - logCacheAndClear(projectService.logger); + logSemanticDiagnostics(session, project, imported); + logCacheAndClear(session.logger); // setting compiler options discards module resolution cache - projectService.setCompilerOptionsForInferredProjects({ module: ts.ModuleKind.AMD, noLib: true, target: ts.ScriptTarget.ES5 }); - logSemanticDiagnostics(projectService, project, imported); - logCacheAndClear(projectService.logger); - baselineTsserverLogs("cachingFileSystemInformation", "works using legacy resolution logic", projectService); + setCompilerOptionsForInferredProjectsRequestForSession({ + module: ts.server.protocol.ModuleKind.AMD, + noLib: true, + target: ts.server.protocol.ScriptTarget.ES5, + }, session); + logSemanticDiagnostics(session, project, imported); + logCacheAndClear(session.logger); + baselineTsserverLogs("cachingFileSystemInformation", "works using legacy resolution logic", session); function editContent(newContent: string) { rootScriptInfo.editContent(0, rootContent.length, newContent); @@ -178,22 +183,25 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS }; const host = createServerHost([root]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.setCompilerOptionsForInferredProjects({ module: ts.ModuleKind.AMD, noLib: true }); + const session = new TestSession(host); + setCompilerOptionsForInferredProjectsRequestForSession({ + module: ts.server.protocol.ModuleKind.AMD, + noLib: true, + }, session); const logCacheAndClear = createLoggerTrackingHostCalls(host); - projectService.openClientFile(root.path); - const project = projectService.inferredProjects[0]; + openFilesForSession([root], session); + const project = session.getProjectService().inferredProjects[0]; const rootScriptInfo = project.getRootScriptInfos()[0]; assert.equal(rootScriptInfo.fileName, root.path); - logSemanticDiagnostics(projectService, project, root); - logCacheAndClear(projectService.logger); + logSemanticDiagnostics(session, project, root); + logCacheAndClear(session.logger); host.writeFile(imported.path, imported.content); host.runQueuedTimeoutCallbacks(); - logSemanticDiagnostics(projectService, project, root); - logCacheAndClear(projectService.logger); - baselineTsserverLogs("cachingFileSystemInformation", "loads missing files from disk", projectService); + logSemanticDiagnostics(session, project, root); + logCacheAndClear(session.logger); + baselineTsserverLogs("cachingFileSystemInformation", "loads missing files from disk", session); }); it("when calling goto definition of module", () => { @@ -239,7 +247,7 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS }; const projectFiles = [clientFile, anotherModuleFile, moduleFile, tsconfigFile]; const host = createServerHost(projectFiles); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([clientFile], session); const logCacheAndClear = createLoggerTrackingHostCalls(host); @@ -319,19 +327,19 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS }; const projectFiles = [file1, file2, es2016LibFile, tsconfigFile]; const host = createServerHost(projectFiles, { useCaseSensitiveFileNames }); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(file1.path); + const session = new TestSession(host); + openFilesForSession([file1], session); const logCacheAndClear = createLoggerTrackingHostCalls(host); // Create file cookie.ts host.writeFile(file3.path, file3.content); host.runQueuedTimeoutCallbacks(); - logCacheAndClear(projectService.logger); + logCacheAndClear(session.logger); - projectService.openClientFile(file3.path); - logCacheAndClear(projectService.logger); - baselineTsserverLogs("cachingFileSystemInformation", `watchDirectories for config file with case ${useCaseSensitiveFileNames ? "" : "in"}sensitive file system`, projectService); + openFilesForSession([file3], session); + logCacheAndClear(session.logger); + baselineTsserverLogs("cachingFileSystemInformation", `watchDirectories for config file with case ${useCaseSensitiveFileNames ? "" : "in"}sensitive file system`, session); }); } verifyWatchDirectoriesCaseSensitivity(/*useCaseSensitiveFileNames*/ false); @@ -359,12 +367,12 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS const files = [file1, file2, tsconfig, libFile]; const host = createServerHost(files); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - service.openClientFile(file1.path); + const session = new TestSession(host); + openFilesForSession([file1], session); - const project = service.configuredProjects.get(tsconfig.path)!; - logSemanticDiagnostics(service, project, file1); - logSemanticDiagnostics(service, project, file2); + const project = session.getProjectService().configuredProjects.get(tsconfig.path)!; + logSemanticDiagnostics(session, project, file1); + logSemanticDiagnostics(session, project, file2); const debugTypesFile: File = { path: `${projectLocation}/node_modules/debug/index.d.ts`, @@ -374,9 +382,9 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions host.runQueuedTimeoutCallbacks(); // Actual update - logSemanticDiagnostics(service, project, file1); - logSemanticDiagnostics(service, project, file2); - baselineTsserverLogs("cachingFileSystemInformation", `includes the parent folder FLLs in ${resolution} module resolution mode`, service); + logSemanticDiagnostics(session, project, file1); + logSemanticDiagnostics(session, project, file2); + baselineTsserverLogs("cachingFileSystemInformation", `includes the parent folder FLLs in ${resolution} module resolution mode`, session); } it("Includes the parent folder FLLs in node module resolution mode", () => { @@ -428,9 +436,12 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS `, }); const host = createServerHost([app, libFile, tsconfigJson, packageJson]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.setHostConfiguration({ preferences: { includePackageJsonAutoImports: "off" } }); - projectService.openClientFile(app.path); + const session = new TestSession(host); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.Configure, + arguments: { preferences: { includePackageJsonAutoImports: "off" } }, + }); + openFilesForSession([app], session); let npmInstallComplete = false; @@ -532,7 +543,7 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS baselineTsserverLogs( "cachingFileSystemInformation", `npm install works when ${timeoutDuringPartialInstallation ? "timeout occurs inbetween installation" : "timeout occurs after installation"}`, - projectService, + session, ); function verifyAfterPartialOrCompleteNpmInstall() { @@ -542,7 +553,7 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS host.runQueuedTimeoutCallbacks(); // Actual update } else { - projectService.testhost.logTimeoutQueueLength(); + session.host.baselineHost("After partial npm install"); } } } @@ -568,11 +579,11 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS const files = [app, tsconfig, libFile]; const host = createServerHost(files); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - service.openClientFile(app.path); + const session = new TestSession(host); + openFilesForSession([app], session); - const project = service.configuredProjects.get(tsconfig.path)!; - logSemanticDiagnostics(service, project, app); + const project = session.getProjectService().configuredProjects.get(tsconfig.path)!; + logSemanticDiagnostics(session, project, app); const debugTypesFile: File = { path: `${projectLocation}/node_modules/@types/debug/index.d.ts`, @@ -587,8 +598,8 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS }; host.writeFile(debugTypesFile.path, debugTypesFile.content); host.runQueuedTimeoutCallbacks(); - logSemanticDiagnostics(service, project, app); - baselineTsserverLogs("cachingFileSystemInformation", "when node_modules dont receive event for the @types file addition", service); + logSemanticDiagnostics(session, project, app); + baselineTsserverLogs("cachingFileSystemInformation", "when node_modules dont receive event for the @types file addition", session); }); it("when creating new file in symlinked folder", () => { @@ -615,10 +626,10 @@ describe("unittests:: tsserver:: CachingFileSystemInformation:: tsserverProjectS }), }; const host = createServerHost([module1, module2, symlink, config, libFile]); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - service.openClientFile(`${symlink.path}/module2.ts`); + const session = new TestSession(host); + openFilesForSession([`${symlink.path}/module2.ts`], session); host.writeFile(`${symlink.path}/module3.ts`, `import * as M from "folder1/module1";`); host.runQueuedTimeoutCallbacks(); - baselineTsserverLogs("cachingFileSystemInformation", "when creating new file in symlinked folder", service); + baselineTsserverLogs("cachingFileSystemInformation", "when creating new file in symlinked folder", session); }); }); diff --git a/src/testRunner/unittests/tsserver/cancellationToken.ts b/src/testRunner/unittests/tsserver/cancellationToken.ts index ce29913f2af67..8a41996a29474 100644 --- a/src/testRunner/unittests/tsserver/cancellationToken.ts +++ b/src/testRunner/unittests/tsserver/cancellationToken.ts @@ -1,14 +1,10 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { jsonToReadableText, } from "../helpers"; import { baselineTsserverLogs, - createSession, - TestServerCancellationToken, + TestSession, TestSessionRequest, } from "../helpers/tsserver"; import { @@ -33,13 +29,7 @@ describe("unittests:: tsserver:: cancellationToken", () => { content: "let xyz = 1;", }; const host = createServerHost([f1]); - const cancellationToken: ts.server.ServerCancellationToken = { - isCancellationRequested: () => false, - setRequest: requestId => session.logger.log(`ServerCancellationToken:: Cancellation Request id:: ${requestId}`), - resetRequest: ts.noop, - }; - - const session = createSession(host, { cancellationToken, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession({ host, useCancellationToken: true }); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.Open, @@ -75,13 +65,9 @@ describe("unittests:: tsserver:: cancellationToken", () => { }; const host = createServerHost([f1, config]); - const logger = createLoggerWithInMemoryLogs(host); - const cancellationToken = new TestServerCancellationToken(logger); - const session = createSession(host, { - canUseEvents: true, - eventHandler: ts.noop, - cancellationToken, - logger, + const session = new TestSession({ + host, + useCancellationToken: true, }); { session.executeCommandSeq({ @@ -112,9 +98,9 @@ describe("unittests:: tsserver:: cancellationToken", () => { }); // cancel previously issued Geterr - cancellationToken.setRequestToCancel(getErrId); + session.serverCancellationToken.setRequestToCancel(getErrId); host.runQueuedTimeoutCallbacks(); - cancellationToken.resetToken(); + session.serverCancellationToken.resetToken(); } { const getErrId = session.getNextSeq(); @@ -126,10 +112,10 @@ describe("unittests:: tsserver:: cancellationToken", () => { // run first step host.runQueuedTimeoutCallbacks(); - cancellationToken.setRequestToCancel(getErrId); + session.serverCancellationToken.setRequestToCancel(getErrId); host.runQueuedImmediateCallbacks(); - cancellationToken.resetToken(); + session.serverCancellationToken.resetToken(); } { session.executeCommandSeq({ @@ -141,7 +127,7 @@ describe("unittests:: tsserver:: cancellationToken", () => { // the semanticDiag message host.runQueuedImmediateCallbacks(); host.runQueuedImmediateCallbacks(); - cancellationToken.resetToken(); + session.serverCancellationToken.resetToken(); } { session.executeCommandSeq({ @@ -171,14 +157,10 @@ describe("unittests:: tsserver:: cancellationToken", () => { }), }; const host = createServerHost([f1, config]); - const logger = createLoggerWithInMemoryLogs(host); - const cancellationToken = new TestServerCancellationToken(logger, /*cancelAfterRequest*/ 3); - const session = createSession(host, { - canUseEvents: true, - eventHandler: ts.noop, - cancellationToken, + const session = new TestSession({ + host, throttleWaitMilliseconds: 0, - logger, + useCancellationToken: 3, }); { session.executeCommandSeq({ @@ -216,7 +198,7 @@ describe("unittests:: tsserver:: cancellationToken", () => { // Set the next request to be cancellable // The cancellation token will cancel the request the third time // isCancellationRequested() is called. - cancellationToken.setRequestToCancel(session.getNextSeq()); + session.serverCancellationToken.setRequestToCancel(session.getNextSeq()); let operationCanceledExceptionThrown = false; try { diff --git a/src/testRunner/unittests/tsserver/compileOnSave.ts b/src/testRunner/unittests/tsserver/compileOnSave.ts index 8289832dab372..2837da600fb56 100644 --- a/src/testRunner/unittests/tsserver/compileOnSave.ts +++ b/src/testRunner/unittests/tsserver/compileOnSave.ts @@ -1,6 +1,6 @@ import { createLoggerWithInMemoryLogs, - Logger, + LoggerWithInMemoryLogs, } from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { @@ -8,7 +8,6 @@ import { } from "../helpers"; import { baselineTsserverLogs, - createSession, openExternalProjectForSession, openFilesForSession, protocolTextSpanFromSubstring, @@ -61,7 +60,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => { const { moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile } = files(); const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([moduleFile1, file1Consumer1], session); @@ -108,7 +107,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { it("should be up-to-date with the reference map changes", () => { const { moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile } = files(); const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([moduleFile1, file1Consumer1], session); @@ -181,7 +180,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { it("should be up-to-date with changes made in non-open files", () => { const { moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile } = files(); const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([moduleFile1], session); @@ -214,7 +213,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { it("should be up-to-date with deleted files", () => { const { moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile } = files(); const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([moduleFile1], session); session.executeCommandSeq({ @@ -245,7 +244,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { it("should be up-to-date with newly created files", () => { const { moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile } = files(); const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([moduleFile1], session); session.executeCommandSeq({ @@ -297,7 +296,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { }; const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([moduleFile1, file1Consumer1], session); session.executeCommandSeq({ @@ -344,7 +343,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { it("should return all files if a global file changed shape", () => { const { moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile } = files(); const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([globalFile3], session); @@ -375,7 +374,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { }; const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([moduleFile1], session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.CompileOnSaveAffectedFileList, @@ -397,7 +396,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { }; const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([moduleFile1], session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.CompileOnSaveAffectedFileList, @@ -423,7 +422,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { }; const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile2, configFile, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([moduleFile1, file1Consumer1], session); session.executeCommandSeq({ @@ -446,7 +445,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { }; const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([moduleFile1], session); session.executeCommandSeq({ @@ -481,7 +480,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { }; const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([moduleFile1], session); session.executeCommandSeq({ @@ -509,7 +508,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { content: `import {y} from "./file1Consumer1";`, }; const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer1Consumer1, globalFile3, configFile, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([moduleFile1, file1Consumer1], session); session.executeCommandSeq({ @@ -561,7 +560,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { export var t2 = 10;`, }; const host = createServerHost([file1, file2, configFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1, file2], session); session.executeCommandSeq({ @@ -579,7 +578,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { const configFile2: File = { path: "/a/c/tsconfig.json", content: `{ "compileOnSave": true }` }; const host = createServerHost([file1, file2, file3, configFile1, configFile2]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1, file2, file3], session); session.executeCommandSeq({ @@ -598,7 +597,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { export var x = Foo();`, }; const host = createServerHost([moduleFile1, referenceFile1, configFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([referenceFile1], session); host.deleteFile(moduleFile1.path); @@ -623,7 +622,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { export var x = Foo();`, }; const host = createServerHost([referenceFile1, configFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([referenceFile1], session); session.executeCommandSeq({ @@ -653,7 +652,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { }), }; const host = createServerHost([dtsFile, f2, config]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([dtsFile], session); openFilesForSession([f2], session); session.executeCommandSeq({ @@ -723,7 +722,7 @@ describe("unittests:: tsserver:: compileOnSave:: affected list", () => { }), }; const host = createServerHost([f1, f2, config]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([f1], session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.CompileOnSaveAffectedFileList, @@ -745,7 +744,7 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => { test("\r\n", logger); baselineTsserverLogs("compileOnSave", "line endings", { logger }); - function test(newLine: string, logger: Logger) { + function test(newLine: string, logger: LoggerWithInMemoryLogs) { const lines = ["var x = 1;", "var y = 2;"]; const path = "/a/app"; const f = { @@ -755,7 +754,7 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => { const host = createServerHost([f], { newLine }); logger.host = host; logger.log(`currentDirectory:: ${host.getCurrentDirectory()} useCaseSensitiveFileNames: ${host.useCaseSensitiveFileNames} newLine: ${host.newLine}`); - const session = createSession(host, { logger }); + const session = new TestSession({ host, logger }); openFilesForSession([f], session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.CompileOnSaveEmitFile, @@ -779,7 +778,7 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => { content: `{}`, }; const host = createServerHost([file1, file2, configFile, libFile], { newLine: "\r\n" }); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1, file2], session); session.executeCommandSeq({ @@ -804,9 +803,9 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => { path: "/a/b/file3.js", content: "console.log('file3');", }; - const externalProjectName = "/a/b/externalproject"; + const projectFileName = "/a/b/externalproject"; const host = createServerHost([file1, file2, file3, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openExternalProjectForSession({ rootFiles: toExternalFiles([file1.path, file2.path]), options: { @@ -814,7 +813,7 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => { outFile: "dist.js", compileOnSave: true, }, - projectFileName: externalProjectName, + projectFileName, }, session); session.executeCommandSeq({ @@ -831,9 +830,9 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => { path: `/root/TypeScriptProject3/TypeScriptProject3/${inputFileName}`, content: "consonle.log('file1');", }; - const externalProjectName = "/root/TypeScriptProject3/TypeScriptProject3/TypeScriptProject3.csproj"; + const projectFileName = "/root/TypeScriptProject3/TypeScriptProject3/TypeScriptProject3.csproj"; const host = createServerHost([file1, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openExternalProjectForSession({ rootFiles: toExternalFiles([file1.path]), options: { @@ -841,7 +840,7 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => { sourceMap: true, compileOnSave: true, }, - projectFileName: externalProjectName, + projectFileName, }, session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.CompileOnSaveEmitFile, @@ -883,7 +882,7 @@ describe("unittests:: tsserver:: compileOnSave:: EmitFile test", () => { content: "const y = 2;", }; const host = createServerHost([file1, file2, config, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1], session); session.executeCommandSeq({ @@ -954,7 +953,7 @@ function bar() { }; const files = [file1, file2, file3, ...(hasModule ? [module] : ts.emptyArray)]; const host = createServerHost([...files, config, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1, file2], session); session.executeCommandSeq({ @@ -1060,7 +1059,7 @@ describe("unittests:: tsserver:: compileOnSave:: CompileOnSaveAffectedFileListRe }; const files = [libFile, core, app1, app2, app1Config, app2Config]; const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([app1, app2, core], session); insertString(session, app1); insertString(session, app2); diff --git a/src/testRunner/unittests/tsserver/completions.ts b/src/testRunner/unittests/tsserver/completions.ts index 787d034e7aee3..472ac18c99549 100644 --- a/src/testRunner/unittests/tsserver/completions.ts +++ b/src/testRunner/unittests/tsserver/completions.ts @@ -1,14 +1,11 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { jsonToReadableText, } from "../helpers"; import { baselineTsserverLogs, - createSession, openFilesForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -32,7 +29,7 @@ describe("unittests:: tsserver:: completions", () => { }; const host = createServerHost([aTs, bTs, tsconfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([aTs, bTs], session); const requestLocation: ts.server.protocol.FileLocationRequestArgs = { @@ -182,11 +179,7 @@ export interface BrowserRouterProps { ]; const host = createServerHost(files, { windowsStyleRoot: "c:/" }); - const logger = createLoggerWithInMemoryLogs(host); - const session = createSession(host, { - globalTypingsCacheLocation, - logger, - }); + const session = new TestSession({ host, globalTypingsCacheLocation }); openFilesForSession([appFile], session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.CompletionInfo, diff --git a/src/testRunner/unittests/tsserver/completionsIncomplete.ts b/src/testRunner/unittests/tsserver/completionsIncomplete.ts index d2bcec05bc993..3467e99090efe 100644 --- a/src/testRunner/unittests/tsserver/completionsIncomplete.ts +++ b/src/testRunner/unittests/tsserver/completionsIncomplete.ts @@ -1,11 +1,8 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { baselineTsserverLogs, - createSession, openFilesForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -170,7 +167,7 @@ describe("unittests:: tsserver:: completionsIncomplete", () => { function setup(files: File[]) { const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); const projectService = session.getProjectService(); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.Configure, diff --git a/src/testRunner/unittests/tsserver/configFileSearch.ts b/src/testRunner/unittests/tsserver/configFileSearch.ts index fb416dedffd94..bac7a19820298 100644 --- a/src/testRunner/unittests/tsserver/configFileSearch.ts +++ b/src/testRunner/unittests/tsserver/configFileSearch.ts @@ -1,9 +1,8 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import { baselineTsserverLogs, - createProjectService, + closeFilesForSession, + openFilesForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -22,17 +21,17 @@ describe("unittests:: tsserver:: configFileSearch:: searching for config file", content: "{}", }; const host = createServerHost([f1, configFile]); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a"); + const session = new TestSession(host); + openFilesForSession([{ file: f1, projectRootPath: "/a" }], session); - service.closeClientFile(f1.path); - service.openClientFile(f1.path); - baselineTsserverLogs("configFileSerach", "should stop at projectRootPath if given", service); + closeFilesForSession([f1], session); + openFilesForSession([f1], session); + baselineTsserverLogs("configFileSearch", "should stop at projectRootPath if given", session); }); it("should use projectRootPath when searching for inferred project again", () => { - const projectDir = "/a/b/projects/project"; - const configFileLocation = `${projectDir}/src`; + const projectRootPath = "/a/b/projects/project"; + const configFileLocation = `${projectRootPath}/src`; const f1 = { path: `${configFileLocation}/file1.ts`, content: "", @@ -46,18 +45,18 @@ describe("unittests:: tsserver:: configFileSearch:: searching for config file", content: "{}", }; const host = createServerHost([f1, libFile, configFile, configFile2]); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir); + const session = new TestSession(host); + openFilesForSession([{ file: f1, projectRootPath }], session); // Delete config file - should create inferred project and not configured project host.deleteFile(configFile.path); host.runQueuedTimeoutCallbacks(); - baselineTsserverLogs("configFileSearch", "should use projectRootPath when searching for inferred project again", service); + baselineTsserverLogs("configFileSearch", "should use projectRootPath when searching for inferred project again", session); }); it("should use projectRootPath when searching for inferred project again 2", () => { - const projectDir = "/a/b/projects/project"; - const configFileLocation = `${projectDir}/src`; + const projectRootPath = "/a/b/projects/project"; + const configFileLocation = `${projectRootPath}/src`; const f1 = { path: `${configFileLocation}/file1.ts`, content: "", @@ -71,17 +70,17 @@ describe("unittests:: tsserver:: configFileSearch:: searching for config file", content: "{}", }; const host = createServerHost([f1, libFile, configFile, configFile2]); - const service = createProjectService(host, { + const session = new TestSession({ + host, useSingleInferredProject: true, useInferredProjectPerProjectRoot: true, - logger: createLoggerWithInMemoryLogs(host), }); - service.openClientFile(f1.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectDir); + openFilesForSession([{ file: f1, projectRootPath }], session); // Delete config file - should create inferred project with project root path set host.deleteFile(configFile.path); host.runQueuedTimeoutCallbacks(); - baselineTsserverLogs("configFileSearch", "should use projectRootPath when searching for inferred project again 2", service); + baselineTsserverLogs("configFileSearch", "should use projectRootPath when searching for inferred project again 2", session); }); describe("when the opened file is not from project root", () => { @@ -96,13 +95,13 @@ describe("unittests:: tsserver:: configFileSearch:: searching for config file", }; function openClientFile(files: File[]) { const host = createServerHost(files); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, "/a/b/projects/proj"); - return { host, projectService }; + const session = new TestSession(host); + openFilesForSession([{ file, projectRootPath: "/a/b/projects/proj" }], session); + return { host, session }; } it("tsconfig for the file exists", () => { - const { host, projectService } = openClientFile([file, libFile, tsconfig]); + const { host, session } = openClientFile([file, libFile, tsconfig]); host.deleteFile(tsconfig.path); host.runQueuedTimeoutCallbacks(); @@ -110,11 +109,11 @@ describe("unittests:: tsserver:: configFileSearch:: searching for config file", host.writeFile(tsconfig.path, tsconfig.content); host.runQueuedTimeoutCallbacks(); - baselineTsserverLogs("configFileSearch", "tsconfig for the file exists", projectService); + baselineTsserverLogs("configFileSearch", "tsconfig for the file exists", session); }); it("tsconfig for the file does not exist", () => { - const { host, projectService } = openClientFile([file, libFile]); + const { host, session } = openClientFile([file, libFile]); host.writeFile(tsconfig.path, tsconfig.content); host.runQueuedTimeoutCallbacks(); @@ -122,7 +121,7 @@ describe("unittests:: tsserver:: configFileSearch:: searching for config file", host.deleteFile(tsconfig.path); host.runQueuedTimeoutCallbacks(); - baselineTsserverLogs("configFileSearch", "tsconfig for the file does not exist", projectService); + baselineTsserverLogs("configFileSearch", "tsconfig for the file does not exist", session); }); }); @@ -131,9 +130,9 @@ describe("unittests:: tsserver:: configFileSearch:: searching for config file", it(scenario, () => { const path = `/root/teams/VSCode68/Shared Documents/General/jt-ts-test-workspace/x.js`; const host = createServerHost([libFile, { path, content: "const x = 10" }], { useCaseSensitiveFileNames: true }); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - service.openClientFile(path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRootPath); - baselineTsserverLogs("configFileSearch", scenario, service); + const session = new TestSession(host); + openFilesForSession([{ file: path, projectRootPath }], session); + baselineTsserverLogs("configFileSearch", scenario, session); }); } verifyConfigFileWatch("when projectRootPath is not present", /*projectRootPath*/ undefined); diff --git a/src/testRunner/unittests/tsserver/configuredProjects.ts b/src/testRunner/unittests/tsserver/configuredProjects.ts index 3cc6ce0367120..483435b5fa4ef 100644 --- a/src/testRunner/unittests/tsserver/configuredProjects.ts +++ b/src/testRunner/unittests/tsserver/configuredProjects.ts @@ -1,6 +1,3 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { jsonToReadableText, @@ -14,11 +11,11 @@ import { } from "../helpers/tscWatch"; import { baselineTsserverLogs, - createProjectService, - createSession, + closeFilesForSession, logConfiguredProjectsHasOpenRefStatus, logInferredProjectsOrphanStatus, openFilesForSession, + TestSession, verifyGetErrRequest, } from "../helpers/tsserver"; import { @@ -54,13 +51,9 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { }; const host = createServerHost([configFile, libFile, file1, file2, file3]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); - - assert(configFileName, "should find config file"); - assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${jsonToReadableText(configFileErrors)}`); - - baselineTsserverLogs("configuredProjects", "create configured project without file list", projectService); + const session = new TestSession(host); + openFilesForSession([file1], session); + baselineTsserverLogs("configuredProjects", "create configured project without file list", session); }); it("create configured project with the file list", () => { @@ -86,13 +79,9 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { }; const host = createServerHost([configFile, libFile, file1, file2, file3]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); - - assert(configFileName, "should find config file"); - assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${jsonToReadableText(configFileErrors)}`); - - baselineTsserverLogs("configuredProjects", "create configured project with the file list", projectService); + const session = new TestSession(host); + openFilesForSession([file1], session); + baselineTsserverLogs("configuredProjects", "create configured project with the file list", session); }); it("add and then remove a config file in a folder with loose files", () => { @@ -113,9 +102,8 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { const host = createServerHost([libFile, commonFile1, commonFile2]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(commonFile1.path); - projectService.openClientFile(commonFile2.path); + const session = new TestSession(host); + openFilesForSession([commonFile1, commonFile2], session); // Add a tsconfig file host.writeFile(configFile.path, configFile.content); @@ -125,7 +113,7 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { host.deleteFile(configFile.path); host.runQueuedTimeoutCallbacks(); // Refresh inferred projects - baselineTsserverLogs("configuredProjects", "add and then remove a config file in a folder with loose files", projectService); + baselineTsserverLogs("configuredProjects", "add and then remove a config file in a folder with loose files", session); }); it("add new files to a configured project without file list", () => { @@ -134,13 +122,13 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { content: `{}`, }; const host = createServerHost([commonFile1, libFile, configFile]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(commonFile1.path); + const session = new TestSession(host); + openFilesForSession([commonFile1], session); // add a new ts file host.writeFile(commonFile2.path, commonFile2.content); host.runQueuedTimeoutCallbacks(); - baselineTsserverLogs("configuredProjects", "add new files to a configured project without file list", projectService); + baselineTsserverLogs("configuredProjects", "add new files to a configured project without file list", session); }); it("should ignore non-existing files specified in the config file", () => { @@ -155,10 +143,9 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { }`, }; const host = createServerHost([commonFile1, commonFile2, configFile]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(commonFile1.path); - projectService.openClientFile(commonFile2.path); - baselineTsserverLogs("configuredProjects", "should ignore non-existing files specified in the config file", projectService); + const session = new TestSession(host); + openFilesForSession([commonFile1, commonFile2], session); + baselineTsserverLogs("configuredProjects", "should ignore non-existing files specified in the config file", session); }); it("handle recreated files correctly", () => { @@ -167,8 +154,8 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { content: `{}`, }; const host = createServerHost([commonFile1, commonFile2, configFile]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(commonFile1.path); + const session = new TestSession(host); + openFilesForSession([commonFile1], session); // delete commonFile2 host.deleteFile(commonFile2.path); @@ -177,7 +164,7 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { // re-add commonFile2 host.writeFile(commonFile2.path, commonFile2.content); host.runQueuedTimeoutCallbacks(); - baselineTsserverLogs("configuredProjects", "handle recreated files correctly", projectService); + baselineTsserverLogs("configuredProjects", "handle recreated files correctly", session); }); it("files explicitly excluded in config file", () => { @@ -194,11 +181,10 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { }; const host = createServerHost([commonFile1, commonFile2, excludedFile1, configFile]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); + openFilesForSession([commonFile1, excludedFile1], session); - projectService.openClientFile(commonFile1.path); - projectService.openClientFile(excludedFile1.path); - baselineTsserverLogs("configuredProjects", "files explicitly excluded in config file", projectService); + baselineTsserverLogs("configuredProjects", "files explicitly excluded in config file", session); }); it("should properly handle module resolution changes in config file", () => { @@ -229,10 +215,8 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { }; const files = [file1, nodeModuleFile, classicModuleFile, configFile, randomFile]; const host = createServerHost(files); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(file1.path); - projectService.openClientFile(nodeModuleFile.path); - projectService.openClientFile(classicModuleFile.path); + const session = new TestSession(host); + openFilesForSession([file1, nodeModuleFile, classicModuleFile], session); host.writeFile( configFile.path, @@ -246,11 +230,11 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { host.runQueuedTimeoutCallbacks(); // will not remove project 1 - logInferredProjectsOrphanStatus(projectService); + logInferredProjectsOrphanStatus(session); // Open random file and it will reuse first inferred project - projectService.openClientFile(randomFile.path); - baselineTsserverLogs("configuredProjects", "should properly handle module resolution changes in config file", projectService); + openFilesForSession([randomFile], session); + baselineTsserverLogs("configuredProjects", "should properly handle module resolution changes in config file", session); }); it("should keep the configured project when the opened file is referenced by the project but not its root", () => { @@ -272,11 +256,11 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { }`, }; const host = createServerHost([file1, file2, configFile]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(file1.path); - projectService.closeClientFile(file1.path); - projectService.openClientFile(file2.path); - baselineTsserverLogs("configuredProjects", "should keep the configured project when the opened file is referenced by the project but not its root", projectService); + const session = new TestSession(host); + openFilesForSession([file1], session); + closeFilesForSession([file1], session); + openFilesForSession([file2], session); + baselineTsserverLogs("configuredProjects", "should keep the configured project when the opened file is referenced by the project but not its root", session); }); it("should tolerate config file errors and still try to build a project", () => { @@ -291,9 +275,9 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { }`, }; const host = createServerHost([commonFile1, commonFile2, libFile, configFile]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(commonFile1.path); - baselineTsserverLogs("configuredProjects", "should tolerate config file errors and still try to build a project", projectService); + const session = new TestSession(host); + openFilesForSession([commonFile1], session); + baselineTsserverLogs("configuredProjects", "should tolerate config file errors and still try to build a project", session); }); it("should reuse same project if file is opened from the configured project that has no open files", () => { @@ -315,16 +299,16 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { }`, }; const host = createServerHost([file1, file2, configFile, libFile]); - const projectService = createProjectService(host, { useSingleInferredProject: true, logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(file1.path); - logConfiguredProjectsHasOpenRefStatus(projectService); // file1 + const session = new TestSession({ host, useSingleInferredProject: true }); + openFilesForSession([file1], session); + logConfiguredProjectsHasOpenRefStatus(session); // file1 - projectService.closeClientFile(file1.path); - logConfiguredProjectsHasOpenRefStatus(projectService); // No open files + closeFilesForSession([file1], session); + logConfiguredProjectsHasOpenRefStatus(session); // No open files - projectService.openClientFile(file2.path); - logConfiguredProjectsHasOpenRefStatus(projectService); // file2 - baselineTsserverLogs("configuredProjects", "should reuse same project if file is opened from the configured project that has no open files", projectService); + openFilesForSession([file2], session); + logConfiguredProjectsHasOpenRefStatus(session); // file2 + baselineTsserverLogs("configuredProjects", "should reuse same project if file is opened from the configured project that has no open files", session); }); it("should not close configured project after closing last open file, but should be closed on next file open if its not the file from same project", () => { @@ -342,16 +326,16 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { }`, }; const host = createServerHost([file1, configFile, libFile]); - const projectService = createProjectService(host, { useSingleInferredProject: true, logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(file1.path); - logConfiguredProjectsHasOpenRefStatus(projectService); // file1 + const session = new TestSession({ host, useSingleInferredProject: true }); + openFilesForSession([file1], session); + logConfiguredProjectsHasOpenRefStatus(session); // file1 - projectService.closeClientFile(file1.path); - logConfiguredProjectsHasOpenRefStatus(projectService); // No files + closeFilesForSession([file1], session); + logConfiguredProjectsHasOpenRefStatus(session); // No files - projectService.openClientFile(libFile.path); - logConfiguredProjectsHasOpenRefStatus(projectService); // No files + project closed - baselineTsserverLogs("configuredProjects", "should not close configured project after closing last open file, but should be closed on next file open if its not the file from same project", projectService); + openFilesForSession([libFile], session); + logConfiguredProjectsHasOpenRefStatus(session); // No files + project closed + baselineTsserverLogs("configuredProjects", "should not close configured project after closing last open file, but should be closed on next file open if its not the file from same project", session); }); it("open file become a part of configured project if it is referenced from root file", () => { @@ -373,16 +357,13 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { }; const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - - projectService.openClientFile(file1.path); - - projectService.openClientFile(file3.path); + const session = new TestSession(host); + openFilesForSession([file1, file3], session); host.writeFile(configFile.path, configFile.content); host.runQueuedTimeoutCallbacks(); // load configured project from disk + ensureProjectsForOpenFiles - logInferredProjectsOrphanStatus(projectService); - baselineTsserverLogs("configuredProjects", "open file become a part of configured project if it is referenced from root file", projectService); + logInferredProjectsOrphanStatus(session); + baselineTsserverLogs("configuredProjects", "open file become a part of configured project if it is referenced from root file", session); }); it("can correctly update configured project when set of root files has changed (new file on disk)", () => { @@ -400,15 +381,14 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { }; const host = createServerHost([file1, configFile]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - - projectService.openClientFile(file1.path); + const session = new TestSession(host); + openFilesForSession([file1], session); host.writeFile(file2.path, file2.content); host.runQueuedTimeoutCallbacks(); - baselineTsserverLogs("configuredProjects", "can correctly update configured project when set of root files has changed (new file on disk)", projectService); + baselineTsserverLogs("configuredProjects", "can correctly update configured project when set of root files has changed (new file on disk)", session); }); it("can correctly update configured project when set of root files has changed (new file in list of files)", () => { @@ -426,14 +406,13 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { }; const host = createServerHost([file1, file2, configFile]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - - projectService.openClientFile(file1.path); + const session = new TestSession(host); + openFilesForSession([file1], session); host.writeFile(configFile.path, jsonToReadableText({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] })); host.runQueuedTimeoutCallbacks(); - baselineTsserverLogs("configuredProjects", "can correctly update configured project when set of root files has changed (new file in list of files)", projectService); + baselineTsserverLogs("configuredProjects", "can correctly update configured project when set of root files has changed (new file in list of files)", session); }); it("can update configured project when set of root files was not changed", () => { @@ -451,14 +430,13 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { }; const host = createServerHost([file1, file2, configFile]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - - projectService.openClientFile(file1.path); + const session = new TestSession(host); + openFilesForSession([file1], session); host.writeFile(configFile.path, jsonToReadableText({ compilerOptions: { outFile: "out.js" }, files: ["f1.ts", "f2.ts"] })); host.runQueuedTimeoutCallbacks(); - baselineTsserverLogs("configuredProjects", "can update configured project when set of root files was not changed", projectService); + baselineTsserverLogs("configuredProjects", "can update configured project when set of root files was not changed", session); }); it("Open ref of configured project when open file gets added to the project as part of configured file update", () => { @@ -485,43 +463,37 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { const files = [file1, file2, file3, file4]; const host = createServerHost(files.concat(configFile)); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - - projectService.openClientFile(file1.path); - projectService.openClientFile(file2.path); - projectService.openClientFile(file3.path); - projectService.openClientFile(file4.path); + const session = new TestSession(host); + openFilesForSession([file1, file2, file3, file4], session); - logConfiguredProjectsHasOpenRefStatus(projectService); // file1 and file3 + logConfiguredProjectsHasOpenRefStatus(session); // file1 and file3 host.writeFile(configFile.path, "{}"); host.runQueuedTimeoutCallbacks(); - logConfiguredProjectsHasOpenRefStatus(projectService); // file1, file2, file3 - logInferredProjectsOrphanStatus(projectService); + logConfiguredProjectsHasOpenRefStatus(session); // file1, file2, file3 + logInferredProjectsOrphanStatus(session); - projectService.closeClientFile(file1.path); - projectService.closeClientFile(file2.path); - projectService.closeClientFile(file4.path); + closeFilesForSession([file1, file2, file4], session); - logConfiguredProjectsHasOpenRefStatus(projectService); // file3 - logInferredProjectsOrphanStatus(projectService); + logConfiguredProjectsHasOpenRefStatus(session); // file3 + logInferredProjectsOrphanStatus(session); - projectService.openClientFile(file4.path); - logConfiguredProjectsHasOpenRefStatus(projectService); // file3 + openFilesForSession([file4], session); + logConfiguredProjectsHasOpenRefStatus(session); // file3 - projectService.closeClientFile(file3.path); - logConfiguredProjectsHasOpenRefStatus(projectService); // No files + closeFilesForSession([file3], session); + logConfiguredProjectsHasOpenRefStatus(session); // No files const file5: File = { path: "/file5.ts", content: "let zz = 1;", }; host.writeFile(file5.path, file5.content); - projectService.testhost.baselineHost("File5 written"); - projectService.openClientFile(file5.path); + session.host.baselineHost("File5 written"); + openFilesForSession([file5], session); - baselineTsserverLogs("configuredProjects", "Open ref of configured project when open file gets added to the project as part of configured file update", projectService); + baselineTsserverLogs("configuredProjects", "Open ref of configured project when open file gets added to the project as part of configured file update", session); }); it("Open ref of configured project when open file gets added to the project as part of configured file update buts its open file references are all closed when the update happens", () => { @@ -549,30 +521,26 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { const files = [file1, file2, file3]; const hostFiles = files.concat(file4, configFile); const host = createServerHost(hostFiles); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); + openFilesForSession([file1, file2, file3], session); - projectService.openClientFile(file1.path); - projectService.openClientFile(file2.path); - projectService.openClientFile(file3.path); + logConfiguredProjectsHasOpenRefStatus(session); // file1 and file3 - logConfiguredProjectsHasOpenRefStatus(projectService); // file1 and file3 - - projectService.closeClientFile(file1.path); - projectService.closeClientFile(file3.path); - logConfiguredProjectsHasOpenRefStatus(projectService); // No files + closeFilesForSession([file1, file3], session); + logConfiguredProjectsHasOpenRefStatus(session); // No files host.writeFile(configFile.path, "{}"); - projectService.testhost.baselineHost("configFile updated"); + session.host.baselineHost("configFile updated"); // Time out is not yet run so there is project update pending - logConfiguredProjectsHasOpenRefStatus(projectService); // Pending update and file2 might get into the project + logConfiguredProjectsHasOpenRefStatus(session); // Pending update and file2 might get into the project - projectService.openClientFile(file4.path); - logConfiguredProjectsHasOpenRefStatus(projectService); // Pending update and F2 might get into the project + openFilesForSession([file4], session); + logConfiguredProjectsHasOpenRefStatus(session); // Pending update and F2 might get into the project host.runQueuedTimeoutCallbacks(); - logConfiguredProjectsHasOpenRefStatus(projectService); // file2 - logInferredProjectsOrphanStatus(projectService); - baselineTsserverLogs("configuredProjects", "Open ref of configured project when open file gets added to the project as part of configured file update buts its open file references are all closed when the update happens", projectService); + logConfiguredProjectsHasOpenRefStatus(session); // file2 + logInferredProjectsOrphanStatus(session); + baselineTsserverLogs("configuredProjects", "Open ref of configured project when open file gets added to the project as part of configured file update buts its open file references are all closed when the update happens", session); }); it("files are properly detached when language service is disabled", () => { @@ -596,17 +564,17 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { const originalGetFileSize = host.getFileSize; host.getFileSize = (filePath: string) => filePath === f2.path ? ts.server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(f1.path); - logConfiguredProjectsHasOpenRefStatus(projectService); // f1 + const session = new TestSession(host); + openFilesForSession([f1], session); + logConfiguredProjectsHasOpenRefStatus(session); // f1 - projectService.closeClientFile(f1.path); - logConfiguredProjectsHasOpenRefStatus(projectService); // No files + closeFilesForSession([f1], session); + logConfiguredProjectsHasOpenRefStatus(session); // No files for (const f of [f1, f2, f3]) { // All the script infos should be present and contain the project since it is still alive. - const scriptInfo = projectService.getScriptInfoForNormalizedPath(ts.server.toNormalizedPath(f.path))!; - projectService.logger.log(`Containing projects for ${f.path}:: ${scriptInfo.containingProjects.map(p => p.projectName).join(",")}`); + const scriptInfo = session.getProjectService().getScriptInfoForNormalizedPath(ts.server.toNormalizedPath(f.path))!; + session.logger.log(`Containing projects for ${f.path}:: ${scriptInfo.containingProjects.map(p => p.projectName).join(",")}`); } const f4 = { @@ -614,14 +582,14 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { content: "var x = 1", }; host.writeFile(f4.path, f4.content); - projectService.openClientFile(f4.path); - logConfiguredProjectsHasOpenRefStatus(projectService); // No files + openFilesForSession([f4], session); + logConfiguredProjectsHasOpenRefStatus(session); // No files for (const f of [f1, f2, f3]) { // All the script infos should not be present since the project is closed and orphan script infos are collected - assert.isUndefined(projectService.getScriptInfoForNormalizedPath(ts.server.toNormalizedPath(f.path))); + assert.isUndefined(session.getProjectService().getScriptInfoForNormalizedPath(ts.server.toNormalizedPath(f.path))); } - baselineTsserverLogs("configuredProjects", "files are properly detached when language service is disabled", projectService); + baselineTsserverLogs("configuredProjects", "files are properly detached when language service is disabled", session); }); it("syntactic features work even if language service is disabled", () => { @@ -640,7 +608,7 @@ describe("unittests:: tsserver:: ConfiguredProjects", () => { const host = createServerHost([f1, f2, config]); const originalGetFileSize = host.getFileSize; host.getFileSize = (filePath: string) => filePath === f2.path ? ts.server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([f1], session); session.logger.log(`Language languageServiceEnabled:: ${session.getProjectService().configuredProjects.get(config.path)!.languageServiceEnabled}`); @@ -702,7 +670,7 @@ declare var console: { };`, }; const host = createServerHost([barConfig, barIndex, fooConfig, fooIndex, barSymLink, lib2017, libDom]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([fooIndex, barIndex], session); verifyGetErrRequest({ session, files: [barIndex, fooIndex] }); baselineTsserverLogs("configuredProjects", "when multiple projects are open detects correct default project", session); @@ -722,9 +690,9 @@ declare var console: { content: "{}", }; const host = createServerHost([file, app, tsconfig, libFile]); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - service.openClientFile(file.path); - baselineTsserverLogs("configuredProjects", "when file name starts with caret", service); + const session = new TestSession(host); + openFilesForSession([file], session); + baselineTsserverLogs("configuredProjects", "when file name starts with caret", session); }); describe("when creating new file", () => { @@ -762,10 +730,7 @@ declare var console: { } : config, ]); - const session = createSession(host, { - canUseEvents: true, - logger: createLoggerWithInMemoryLogs(host), - }); + const session = new TestSession(host); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.Open, arguments: { @@ -870,8 +835,7 @@ foo();`, const host = createServerHost([barConfig, barIndex, fooBarConfig, fooBarIndex, fooConfig, fooIndex, libFile]); ensureErrorFreeBuild(host, [fooConfig.path]); const fooDts = `/user/username/projects/myproject/foo/lib/index.d.ts`; - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - const service = session.getProjectService(); + const session = new TestSession(host); openFilesForSession([barIndex, fooBarIndex, fooIndex, fooDts], session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.GetApplicableRefactors, @@ -883,7 +847,7 @@ foo();`, endOffset: 1, }, }); - session.logger.log(`Default project for file: ${fooDts}: ${service.tryGetDefaultProjectForFile(ts.server.toNormalizedPath(fooDts))?.projectName}`); + session.logger.log(`Default project for file: ${fooDts}: ${session.getProjectService().tryGetDefaultProjectForFile(ts.server.toNormalizedPath(fooDts))?.projectName}`); baselineTsserverLogs("configuredProjects", "when default configured project does not contain the file", session); }); @@ -923,15 +887,14 @@ foo();`, }; const host = createServerHost([alphaExtendedConfig, aConfig, aFile, bravoExtendedConfig, bConfig, bFile, ...(additionalFiles || ts.emptyArray)]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - return { host, projectService, aFile, bFile, aConfig, bConfig, alphaExtendedConfig, bravoExtendedConfig }; + const session = new TestSession(host); + return { host, session, aFile, bFile, aConfig, bConfig, alphaExtendedConfig, bravoExtendedConfig }; } it("should watch the extended configs of multiple projects", () => { - const { host, projectService, aFile, bFile, bConfig, alphaExtendedConfig, bravoExtendedConfig } = getService(); + const { host, session, aFile, bFile, bConfig, alphaExtendedConfig, bravoExtendedConfig } = getService(); - projectService.openClientFile(aFile.path); - projectService.openClientFile(bFile.path); + openFilesForSession([aFile, bFile], session); host.writeFile( alphaExtendedConfig.path, @@ -964,7 +927,7 @@ foo();`, host.writeFile(alphaExtendedConfig.path, "{}"); host.runQueuedTimeoutCallbacks(); - baselineTsserverLogs("configuredProjects", "should watch the extended configs of multiple projects", projectService); + baselineTsserverLogs("configuredProjects", "should watch the extended configs of multiple projects", session); }); it("should stop watching the extended configs of closed projects", () => { @@ -976,20 +939,16 @@ foo();`, path: `/user/username/projects/myproject/dummy/tsconfig.json`, content: "{}", }; - const { projectService, aFile, bFile } = getService([dummy, dummyConfig]); + const { session, aFile, bFile } = getService([dummy, dummyConfig]); - projectService.openClientFile(aFile.path); - projectService.openClientFile(bFile.path); - projectService.openClientFile(dummy.path); + openFilesForSession([aFile, bFile, dummy], session); - projectService.closeClientFile(bFile.path); - projectService.closeClientFile(dummy.path); - projectService.openClientFile(dummy.path); + closeFilesForSession([bFile, dummy], session); + openFilesForSession([dummy], session); - projectService.closeClientFile(aFile.path); - projectService.closeClientFile(dummy.path); - projectService.openClientFile(dummy.path); - baselineTsserverLogs("configuredProjects", "should stop watching the extended configs of closed projects", projectService); + closeFilesForSession([aFile, dummy], session); + openFilesForSession([dummy], session); + baselineTsserverLogs("configuredProjects", "should stop watching the extended configs of closed projects", session); }); }); }); @@ -1009,12 +968,12 @@ describe("unittests:: tsserver:: ConfiguredProjects:: non-existing directories l }; const host = createServerHost([file1, configFile]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(file1.path); + const session = new TestSession(host); + openFilesForSession([file1], session); host.runQueuedTimeoutCallbacks(); // Since file1 refers to config file as the default project, it needs to be kept alive - baselineTsserverLogs("configuredProjects", "should be tolerated without crashing the server", projectService); + baselineTsserverLogs("configuredProjects", "should be tolerated without crashing the server", session); }); it("should be able to handle @types if input file list is empty", () => { @@ -1038,11 +997,11 @@ describe("unittests:: tsserver:: ConfiguredProjects:: non-existing directories l content: `export const x: number`, }; const host = createServerHost([f, config, t1, t2], { currentDirectory: ts.getDirectoryPath(f.path) }); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); + openFilesForSession([f], session); - projectService.openClientFile(f.path); // Since f refers to config file as the default project, it needs to be kept alive - baselineTsserverLogs("configuredProjects", "should be able to handle @types if input file list is empty", projectService); + baselineTsserverLogs("configuredProjects", "should be able to handle @types if input file list is empty", session); }); it("should tolerate invalid include files that start in subDirectory", () => { @@ -1063,11 +1022,11 @@ describe("unittests:: tsserver:: ConfiguredProjects:: non-existing directories l }), }; const host = createServerHost([f, config, libFile], { useCaseSensitiveFileNames: true }); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); + openFilesForSession([f], session); - projectService.openClientFile(f.path); // Since f refers to config file as the default project, it needs to be kept alive - baselineTsserverLogs("configuredProjects", "should tolerate invalid include files that start in subDirectory", projectService); + baselineTsserverLogs("configuredProjects", "should tolerate invalid include files that start in subDirectory", session); }); it("Changed module resolution reflected when specifying files list", () => { @@ -1089,16 +1048,16 @@ describe("unittests:: tsserver:: ConfiguredProjects:: non-existing directories l }; const files = [file1, file2a, configFile, libFile]; const host = createServerHost(files); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(file1.path); + const session = new TestSession(host); + openFilesForSession([file1], session); host.writeFile(file2.path, file2.content); host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions host.runQueuedTimeoutCallbacks(); // Actual update // On next file open the files file2a should be closed and not watched any more - projectService.openClientFile(file2.path); - baselineTsserverLogs("configuredProjects", "changed module resolution reflected when specifying files list", projectService); + openFilesForSession([file2], session); + baselineTsserverLogs("configuredProjects", "changed module resolution reflected when specifying files list", session); }); it("Failed lookup locations uses parent most node_modules directory", () => { @@ -1128,9 +1087,9 @@ describe("unittests:: tsserver:: ConfiguredProjects:: non-existing directories l nonLibFiles.forEach(f => f.path = root + f.path); const files = nonLibFiles.concat(libFile); const host = createServerHost(files); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(file1.path); - baselineTsserverLogs("configuredProjects", "failed lookup locations uses parent most node_modules directory", projectService); + const session = new TestSession(host); + openFilesForSession([file1], session); + baselineTsserverLogs("configuredProjects", "failed lookup locations uses parent most node_modules directory", session); }); }); @@ -1146,7 +1105,7 @@ describe("unittests:: tsserver:: ConfiguredProjects:: when reading tsconfig file }; const host = createServerHost([file1, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); const originalReadFile = host.readFile; host.readFile = f => { return f === configFile.path ? diff --git a/src/testRunner/unittests/tsserver/declarationFileMaps.ts b/src/testRunner/unittests/tsserver/declarationFileMaps.ts index ed797ea7676fd..7e01ce4f79e9a 100644 --- a/src/testRunner/unittests/tsserver/declarationFileMaps.ts +++ b/src/testRunner/unittests/tsserver/declarationFileMaps.ts @@ -1,6 +1,3 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { jsonToReadableText, @@ -8,7 +5,6 @@ import { import { baselineTsserverLogs, closeFilesForSession, - createSession, openFilesForSession, protocolFileLocationFromSubstring, TestSession, @@ -108,7 +104,7 @@ describe("unittests:: tsserver:: with declaration file maps:: project references function makeSampleProjects(addUserTsConfig?: boolean, keepAllFiles?: boolean) { const host = createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, ...(addUserTsConfig ? [userTsForConfigProject, userTsconfig] : [userTs]), dummyFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); checkDeclarationFiles(aTs, session); checkDeclarationFiles(bTs, session); @@ -285,7 +281,7 @@ describe("unittests:: tsserver:: with declaration file maps:: project references }; const host = createServerHost([aTs, aTsconfig, bTs, bTsconfig, aDts, aDtsMap]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); checkDeclarationFiles(aTs, session); openFilesForSession([bTs], session); @@ -387,7 +383,7 @@ describe("unittests:: tsserver:: with declaration file maps:: project references }; const host = createServerHost([aTs, aTsconfig, bTs, bTsconfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([aTs, bTs], session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.GetEditsForFileRename, @@ -409,7 +405,7 @@ describe("unittests:: tsserver:: with declaration file maps:: project references content: jsonToReadableText(aDtsInlinedSources), }; const host = createServerHost([aTs, aDtsMapInlinedSources, aDts, bTs, bDtsMap, bDts, userTs, dummyFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([userTs], session); // If config file then userConfig project and bConfig project since it is referenced diff --git a/src/testRunner/unittests/tsserver/documentRegistry.ts b/src/testRunner/unittests/tsserver/documentRegistry.ts index 43706ab46806f..01f9dfc124245 100644 --- a/src/testRunner/unittests/tsserver/documentRegistry.ts +++ b/src/testRunner/unittests/tsserver/documentRegistry.ts @@ -1,9 +1,6 @@ import { reportDocumentRegistryStats, } from "../../../harness/incrementalUtils"; -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { jsonToReadableText, @@ -11,10 +8,8 @@ import { import { baselineTsserverLogs, closeFilesForSession, - createProjectService, - createSession, openFilesForSession, - TestProjectService, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -37,88 +32,106 @@ describe("unittests:: tsserver:: documentRegistry:: document registry in project content: jsonToReadableText({ files: ["index.ts"] }), }; - function getProject(service: TestProjectService) { - return service.configuredProjects.get(configFile.path)!; + function getProject(session: TestSession) { + return session.getProjectService().configuredProjects.get(configFile.path)!; } - function checkProject(service: TestProjectService, moduleIsOrphan: boolean) { + function checkProject(session: TestSession, moduleIsOrphan: boolean) { // Update the project - const project = getProject(service); + const project = getProject(session); project.getLanguageService(); - const moduleInfo = service.getScriptInfo(moduleFile.path)!; + const moduleInfo = session.getProjectService().getScriptInfo(moduleFile.path)!; assert.isDefined(moduleInfo); assert.equal(moduleInfo.isOrphan(), moduleIsOrphan); - service.logger.log("DocumentRegistry::"); - service.logger.log(reportDocumentRegistryStats(service.documentRegistry).join("\n")); + session.logger.log("DocumentRegistry::"); + session.logger.log(reportDocumentRegistryStats(session.getProjectService().documentRegistry).join("\n")); } - function createServiceAndHost() { + function createSessionAndHost() { const host = createServerHost([file, moduleFile, libFile, configFile]); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - service.openClientFile(file.path); - checkProject(service, /*moduleIsOrphan*/ false); - return { host, service }; + const session = new TestSession(host); + openFilesForSession([file], session); + checkProject(session, /*moduleIsOrphan*/ false); + return { host, session }; } - function changeFileToNotImportModule(service: TestProjectService) { - const info = service.getScriptInfo(file.path)!; - service.applyChangesToFile(info, ts.singleIterator({ span: { start: 0, length: importModuleContent.length }, newText: "" })); - checkProject(service, /*moduleIsOrphan*/ true); + function changeFileToNotImportModule(session: TestSession) { + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.Change, + arguments: { + file: file.path, + line: 1, + offset: 1, + endLine: 1, + endOffset: importModuleContent.length + 1, + insertString: "", + }, + }); + checkProject(session, /*moduleIsOrphan*/ true); } - function changeFileToImportModule(service: TestProjectService) { - const info = service.getScriptInfo(file.path)!; - service.applyChangesToFile(info, ts.singleIterator({ span: { start: 0, length: 0 }, newText: importModuleContent })); - checkProject(service, /*moduleIsOrphan*/ false); + function changeFileToImportModule(session: TestSession) { + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.Change, + arguments: { + file: file.path, + line: 1, + offset: 1, + endLine: 1, + endOffset: 1, + insertString: importModuleContent, + }, + }); + checkProject(session, /*moduleIsOrphan*/ false); } it("Caches the source file if script info is orphan", () => { - const { service } = createServiceAndHost(); - const project = getProject(service); + const { session } = createSessionAndHost(); + const project = getProject(session); - const moduleInfo = service.getScriptInfo(moduleFile.path)!; + const moduleInfo = session.getProjectService().getScriptInfo(moduleFile.path)!; const sourceFile = moduleInfo.cacheSourceFile!.sourceFile; assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); // edit file - changeFileToNotImportModule(service); + changeFileToNotImportModule(session); assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); // write content back - changeFileToImportModule(service); + changeFileToImportModule(session); assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); - baselineTsserverLogs("documentRegistry", "Caches the source file if script info is orphan", service); + baselineTsserverLogs("documentRegistry", "Caches the source file if script info is orphan", session); }); it("Caches the source file if script info is orphan, and orphan script info changes", () => { - const { host, service } = createServiceAndHost(); - const project = getProject(service); + const { host, session } = createSessionAndHost(); + const project = getProject(session); - const moduleInfo = service.getScriptInfo(moduleFile.path)!; + const moduleInfo = session.getProjectService().getScriptInfo(moduleFile.path)!; const sourceFile = moduleInfo.cacheSourceFile!.sourceFile; assert.equal(project.getSourceFile(moduleInfo.path), sourceFile); // edit file - changeFileToNotImportModule(service); + changeFileToNotImportModule(session); assert.equal(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); const updatedModuleContent = moduleFile.content + "\nexport const b: number;"; host.writeFile(moduleFile.path, updatedModuleContent); // write content back - changeFileToImportModule(service); + changeFileToImportModule(session); assert.notEqual(moduleInfo.cacheSourceFile!.sourceFile, sourceFile); assert.equal(project.getSourceFile(moduleInfo.path), moduleInfo.cacheSourceFile!.sourceFile); assert.equal(moduleInfo.cacheSourceFile!.sourceFile.text, updatedModuleContent); - baselineTsserverLogs("documentRegistry", "Caches the source file if script info is orphan, and orphan script info changes", service); + baselineTsserverLogs("documentRegistry", "Caches the source file if script info is orphan, and orphan script info changes", session); }); }); describe("unittests:: tsserver:: documentRegistry:: works when reusing orphan script info with different scriptKind", () => { it("works when reusing orphan script info with different scriptKind", () => { const host = createServerHost({}); - const session = createSession(host, { useInferredProjectPerProjectRoot: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession({ host, useInferredProjectPerProjectRoot: true }); const newText = "exrpot const x = 10;"; const content = `import x from 'react';\n${newText}`; openFilesForSession([ diff --git a/src/testRunner/unittests/tsserver/duplicatePackages.ts b/src/testRunner/unittests/tsserver/duplicatePackages.ts index ab3c22fdbe1d7..8413022207d8f 100644 --- a/src/testRunner/unittests/tsserver/duplicatePackages.ts +++ b/src/testRunner/unittests/tsserver/duplicatePackages.ts @@ -1,14 +1,11 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { jsonToReadableText, } from "../helpers"; import { baselineTsserverLogs, - createSession, openFilesForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -34,7 +31,7 @@ describe("unittests:: tsserver:: duplicate packages", () => { }; const host = createServerHost([aFooIndex, aFooPackage, bFooIndex, bFooPackage, aUser, bUser, tsconfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([aUser, bUser], session); diff --git a/src/testRunner/unittests/tsserver/dynamicFiles.ts b/src/testRunner/unittests/tsserver/dynamicFiles.ts index a7815617e2262..c8e0d5c045cfc 100644 --- a/src/testRunner/unittests/tsserver/dynamicFiles.ts +++ b/src/testRunner/unittests/tsserver/dynamicFiles.ts @@ -1,14 +1,11 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { baselineTsserverLogs, - createProjectService, - createSession, + closeFilesForSession, openFilesForSession, protocolFileLocationFromSubstring, setCompilerOptionsForInferredProjectsRequestForSession, + TestSession, verifyDynamic, } from "../helpers/tsserver"; import { @@ -26,11 +23,11 @@ function verifyPathRecognizedAsDynamic(subscenario: string, path: string) { var x = 10;`, }; const host = createServerHost([libFile]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(file.path, file.content); - verifyDynamic(projectService, projectService.toPath(file.path)); + const session = new TestSession(host); + openFilesForSession([{ file, content: file.content }], session); + verifyDynamic(session, session.getProjectService().toPath(file.path)); - baselineTsserverLogs("dynamicFiles", subscenario, projectService); + baselineTsserverLogs("dynamicFiles", subscenario, session); }); } @@ -40,7 +37,7 @@ describe("unittests:: tsserver:: dynamicFiles:: Untitled files", () => { const aTs: File = { path: "/proj/a.ts", content: "" }; const tsconfig: File = { path: "/proj/tsconfig.json", content: "{}" }; const host = createServerHost([aTs, tsconfig]); - const session = createSession(host, { useInferredProjectPerProjectRoot: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession({ host, useInferredProjectPerProjectRoot: true }); openFilesForSession([aTs], session); @@ -53,7 +50,7 @@ describe("unittests:: tsserver:: dynamicFiles:: Untitled files", () => { projectRootPath: "/proj", }, }); - verifyDynamic(session.getProjectService(), `/proj/untitled:^untitled-1`); + verifyDynamic(session, `/proj/untitled:^untitled-1`); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.GetCodeFixes, arguments: { @@ -74,23 +71,34 @@ describe("unittests:: tsserver:: dynamicFiles:: Untitled files", () => { content: "{}", }; const host = createServerHost([config, libFile], { useCaseSensitiveFileNames: true, currentDirectory: "/user/username/projects/myproject" }); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, "/user/username/projects/myproject"); - verifyDynamic(service, `/user/username/projects/myproject/${untitledFile}`); + const session = new TestSession(host); + openFilesForSession([{ + file: untitledFile, + content: "const x = 10;", + projectRootPath: "/user/username/projects/myproject", + }], session); + verifyDynamic(session, `/user/username/projects/myproject/${untitledFile}`); const untitled: File = { path: `/user/username/projects/myproject/Untitled-1.ts`, content: "const x = 10;", }; host.writeFile(untitled.path, untitled.content); - service.testhost.logTimeoutQueueLength(); - service.openClientFile(untitled.path, untitled.content, /*scriptKind*/ undefined, "/user/username/projects/myproject"); + openFilesForSession([{ + file: untitled.path, + content: untitled.content, + projectRootPath: "/user/username/projects/myproject", + }], session); - service.closeClientFile(untitledFile); + closeFilesForSession([untitledFile], session); - service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, "/user/username/projects/myproject"); - verifyDynamic(service, `/user/username/projects/myproject/${untitledFile}`); - baselineTsserverLogs("dynamicFiles", "opening untitled files", service); + openFilesForSession([{ + file: untitledFile, + content: "const x = 10;", + projectRootPath: "/user/username/projects/myproject", + }], session); + verifyDynamic(session, `/user/username/projects/myproject/${untitledFile}`); + baselineTsserverLogs("dynamicFiles", "opening untitled files", session); }); it("opening and closing untitled files when projectRootPath is different from currentDirectory", () => { @@ -103,35 +111,49 @@ describe("unittests:: tsserver:: dynamicFiles:: Untitled files", () => { content: "const y = 10", }; const host = createServerHost([config, file, libFile], { useCaseSensitiveFileNames: true }); - const service = createProjectService(host, { useInferredProjectPerProjectRoot: true, logger: createLoggerWithInMemoryLogs(host) }); - service.openClientFile(untitledFile, "const x = 10;", /*scriptKind*/ undefined, "/user/username/projects/myproject"); - verifyDynamic(service, `/user/username/projects/myproject/${untitledFile}`); + const session = new TestSession({ host, useInferredProjectPerProjectRoot: true }); + openFilesForSession([{ + file: untitledFile, + content: "const x = 10;", + projectRootPath: "/user/username/projects/myproject", + }], session); + verifyDynamic(session, `/user/username/projects/myproject/${untitledFile}`); // Close untitled file - service.closeClientFile(untitledFile); + closeFilesForSession([untitledFile], session); // Open file from configured project which should collect inferredProject - service.openClientFile(file.path); - baselineTsserverLogs("dynamicFiles", "opening and closing untitled files when projectRootPath is different from currentDirectory", service); + openFilesForSession([file], session); + baselineTsserverLogs("dynamicFiles", "opening and closing untitled files when projectRootPath is different from currentDirectory", session); }); it("when changing scriptKind of the untitled files", () => { const host = createServerHost([libFile], { useCaseSensitiveFileNames: true }); - const service = createProjectService(host, { useInferredProjectPerProjectRoot: true, logger: createLoggerWithInMemoryLogs(host) }); - service.openClientFile(untitledFile, "const x = 10;", ts.ScriptKind.TS, "/user/username/projects/myproject"); - const program = service.inferredProjects[0].getCurrentProgram()!; + const session = new TestSession({ host, useInferredProjectPerProjectRoot: true }); + openFilesForSession([{ + file: untitledFile, + content: "const x = 10;", + scriptKindName: "TS", + projectRootPath: "/user/username/projects/myproject", + }], session); + const program = session.getProjectService().inferredProjects[0].getCurrentProgram()!; const sourceFile = program.getSourceFile(untitledFile)!; // Close untitled file - service.closeClientFile(untitledFile); + closeFilesForSession([untitledFile], session); // Open untitled file with different mode - service.openClientFile(untitledFile, "const x = 10;", ts.ScriptKind.TSX, "/user/username/projects/myproject"); - const newProgram = service.inferredProjects[0].getCurrentProgram()!; + openFilesForSession([{ + file: untitledFile, + content: "const x = 10;", + scriptKindName: "TSX", + projectRootPath: "/user/username/projects/myproject", + }], session); + const newProgram = session.getProjectService().inferredProjects[0].getCurrentProgram()!; const newSourceFile = newProgram.getSourceFile(untitledFile)!; assert.notStrictEqual(newProgram, program); assert.notStrictEqual(newSourceFile, sourceFile); - baselineTsserverLogs("dynamicFiles", "when changing scriptKind of the untitled files", service); + baselineTsserverLogs("dynamicFiles", "when changing scriptKind of the untitled files", session); }); }); @@ -142,16 +164,16 @@ describe("unittests:: tsserver:: dynamicFiles:: ", () => { content: "var x = 10;", }; const host = createServerHost([libFile], { useCaseSensitiveFileNames: true }); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); setCompilerOptionsForInferredProjectsRequestForSession({ - module: ts.ModuleKind.CommonJS, + module: ts.server.protocol.ModuleKind.CommonJS, allowJs: true, allowSyntheticDefaultImports: true, allowNonTsExtensions: true, }, session); openFilesForSession([{ file: file.path, content: "var x = 10;" }], session); - verifyDynamic(session.getProjectService(), `/${file.path}`); + verifyDynamic(session, `/${file.path}`); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.Quickinfo, @@ -177,11 +199,10 @@ describe("unittests:: tsserver:: dynamicFiles:: ", () => { }; it("with useInferredProjectPerProjectRoot", () => { const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true }); - const session = createSession(host, { useInferredProjectPerProjectRoot: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession({ host, useInferredProjectPerProjectRoot: true }); openFilesForSession([{ file: file.path, projectRootPath: "/user/username/projects/myproject" }], session); - const projectService = session.getProjectService(); - verifyDynamic(projectService, `/user/username/projects/myproject/${file.path}`); + verifyDynamic(session, `/user/username/projects/myproject/${file.path}`); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.GetOutliningSpans, @@ -198,9 +219,13 @@ describe("unittests:: tsserver:: dynamicFiles:: ", () => { it("fails when useInferredProjectPerProjectRoot is false", () => { const host = createServerHost([libFile, configFile, configProjectFile], { useCaseSensitiveFileNames: true }); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); try { - projectService.openClientFile(file.path, file.content, /*scriptKind*/ undefined, "/user/username/projects/myproject"); + openFilesForSession([{ + file, + content: file.content, + projectRootPath: "/user/username/projects/myproject", + }], session); } catch (e) { assert.strictEqual( @@ -209,8 +234,8 @@ describe("unittests:: tsserver:: dynamicFiles:: ", () => { ); } const file2Path = file.path.replace("#1", "#2"); - projectService.openClientFile(file2Path, file.content); - baselineTsserverLogs("dynamicFiles", "dynamic file with projectRootPath fails when useInferredProjectPerProjectRoot is false", projectService); + openFilesForSession([{ file: file2Path, content: file.content }], session); + baselineTsserverLogs("dynamicFiles", "dynamic file with projectRootPath fails when useInferredProjectPerProjectRoot is false", session); }); }); diff --git a/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts b/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts index e2ce5a25190ad..a0cce09a03992 100644 --- a/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts +++ b/src/testRunner/unittests/tsserver/events/largeFileReferenced.ts @@ -1,14 +1,11 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../../harness/tsserverLogger"; import * as ts from "../../../_namespaces/ts"; import { jsonToReadableText, } from "../../helpers"; import { baselineTsserverLogs, - createSession, openFilesForSession, + TestSession, } from "../../helpers/tsserver"; import { createServerHost, @@ -32,8 +29,7 @@ describe("unittests:: tsserver:: events:: LargeFileReferencedEvent with large fi }; files.push(largeFile); const host = createServerHost(files); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - + const session = new TestSession(host); return session; } diff --git a/src/testRunner/unittests/tsserver/events/projectLanguageServiceState.ts b/src/testRunner/unittests/tsserver/events/projectLanguageServiceState.ts index 048b5194d3b18..a66d4a76363df 100644 --- a/src/testRunner/unittests/tsserver/events/projectLanguageServiceState.ts +++ b/src/testRunner/unittests/tsserver/events/projectLanguageServiceState.ts @@ -1,15 +1,11 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../../harness/tsserverLogger"; import * as ts from "../../../_namespaces/ts"; import { jsonToReadableText, } from "../../helpers"; import { baselineTsserverLogs, - createProjectService, - createSession, openFilesForSession, + TestSession, } from "../../helpers/tsserver"; import { createServerHost, @@ -39,7 +35,7 @@ describe("unittests:: tsserver:: events:: ProjectLanguageServiceStateEvent", () const originalGetFileSize = host.getFileSize; host.getFileSize = (filePath: string) => filePath === f2.path ? ts.server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([f1], session); session.logger.log(`Language service enabled: ${session.getProjectService().configuredProjects.get(config.path)!.languageServiceEnabled}`); @@ -69,11 +65,11 @@ describe("unittests:: tsserver:: events:: ProjectLanguageServiceStateEvent", () content: "{}", }; const host = createServerHost([f1, f2, f3, libFile, config]); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - service.openClientFile(f1.path); - const project = service.configuredProjects.get(config.path)!; - service.logger.info(`languageServiceEnabled: ${project.languageServiceEnabled}`); - service.logger.info(`lastFileExceededProgramSize: ${project.lastFileExceededProgramSize}`); - baselineTsserverLogs("events/projectLanguageServiceState", "large file size is determined correctly", service); + const session = new TestSession(host); + openFilesForSession([f1], session); + const project = session.getProjectService().configuredProjects.get(config.path)!; + session.logger.info(`languageServiceEnabled: ${project.languageServiceEnabled}`); + session.logger.info(`lastFileExceededProgramSize: ${project.lastFileExceededProgramSize}`); + baselineTsserverLogs("events/projectLanguageServiceState", "large file size is determined correctly", session); }); }); diff --git a/src/testRunner/unittests/tsserver/events/projectLoading.ts b/src/testRunner/unittests/tsserver/events/projectLoading.ts index f555d6e23c4fe..b636a8008d628 100644 --- a/src/testRunner/unittests/tsserver/events/projectLoading.ts +++ b/src/testRunner/unittests/tsserver/events/projectLoading.ts @@ -1,13 +1,9 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../../harness/tsserverLogger"; import * as ts from "../../../_namespaces/ts"; import { jsonToReadableText, } from "../../helpers"; import { baselineTsserverLogs, - createSession, createSessionWithCustomEventHandler, openExternalProjectForSession, openFilesForSession, @@ -180,10 +176,6 @@ describe("unittests:: tsserver:: events:: ProjectLoadingStart and ProjectLoading }); } - verifyProjectLoadingStartAndFinish("when using event handler", host => createSessionWithCustomEventHandler(host)); - verifyProjectLoadingStartAndFinish("when using default event handler", host => - createSession( - host, - { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }, - )); + verifyProjectLoadingStartAndFinish("when using event handler", createSessionWithCustomEventHandler); + verifyProjectLoadingStartAndFinish("when using default event handler", host => new TestSession(host)); }); diff --git a/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts b/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts index 54c29910a527b..a8199a327054a 100644 --- a/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts +++ b/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts @@ -1,13 +1,9 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../../harness/tsserverLogger"; import * as ts from "../../../_namespaces/ts"; import { jsonToReadableText, } from "../../helpers"; import { baselineTsserverLogs, - createSession, createSessionWithCustomEventHandler, openFilesForSession, TestSession, @@ -411,19 +407,14 @@ describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => { describe("when event handler is not set but session is created with canUseEvents = true", () => { describe("without noGetErrOnBackgroundUpdate, diagnostics for open files are queued", () => { verifyProjectsUpdatedInBackgroundEvent("without noGetErrOnBackgroundUpdate", host => - createSession(host, { - canUseEvents: true, - logger: createLoggerWithInMemoryLogs(host), + new TestSession({ + host, + noGetErrOnBackgroundUpdate: false, })); }); describe("with noGetErrOnBackgroundUpdate, diagnostics for open file are not queued", () => { - verifyProjectsUpdatedInBackgroundEvent("with noGetErrOnBackgroundUpdate", host => - createSession(host, { - canUseEvents: true, - logger: createLoggerWithInMemoryLogs(host), - noGetErrOnBackgroundUpdate: true, - })); + verifyProjectsUpdatedInBackgroundEvent("with noGetErrOnBackgroundUpdate", host => new TestSession(host)); }); }); }); diff --git a/src/testRunner/unittests/tsserver/events/watchEvents.ts b/src/testRunner/unittests/tsserver/events/watchEvents.ts index 5cf06362e3f0b..c60fb4621fb22 100644 --- a/src/testRunner/unittests/tsserver/events/watchEvents.ts +++ b/src/testRunner/unittests/tsserver/events/watchEvents.ts @@ -1,6 +1,6 @@ import { createLoggerWithInMemoryLogs, - Logger, + LoggerWithInMemoryLogs, } from "../../../../harness/tsserverLogger"; import { createWatchUtils, @@ -10,7 +10,6 @@ import * as ts from "../../../_namespaces/ts"; import { baselineTsserverLogs, closeFilesForSession, - createSession, createSessionWithCustomEventHandler, openFilesForSession, TestSession, @@ -32,7 +31,7 @@ describe("unittests:: tsserver:: events:: watchEvents", () => { } function createTestServerHostWithCustomWatch( - logger: Logger, + logger: LoggerWithInMemoryLogs, ) { const idToClose = new Map void>(); const host = logger.host as TestServerHostWithCustomWatch; @@ -89,8 +88,8 @@ describe("unittests:: tsserver:: events:: watchEvents", () => { function updateFileOnHost(session: TestSession, file: string, log: string) { // Change b.ts session.logger.log(log); - session.testhost.writeFile(file, session.testhost.readFile("/user/username/projects/myproject/a.ts")!); - session.testhost.runQueuedTimeoutCallbacks(); + session.host.writeFile(file, session.host.readFile("/user/username/projects/myproject/a.ts")!); + session.host.runQueuedTimeoutCallbacks(); } function addFile(session: TestSession, path: string) { @@ -102,7 +101,7 @@ describe("unittests:: tsserver:: events:: watchEvents", () => { arguments: { id: data.id, path, eventType: "create" }, }) ); - session.testhost.runQueuedTimeoutCallbacks(); + session.host.runQueuedTimeoutCallbacks(); } function changeFile(session: TestSession, path: string) { @@ -114,7 +113,7 @@ describe("unittests:: tsserver:: events:: watchEvents", () => { arguments: { id: data.id, path, eventType: "update" }, }) ); - session.testhost.runQueuedTimeoutCallbacks(); + session.host.runQueuedTimeoutCallbacks(); } function setup() { @@ -131,7 +130,7 @@ describe("unittests:: tsserver:: events:: watchEvents", () => { it("canUseWatchEvents", () => { const { host, logger } = setup(); - const session = createSessionWithCustomEventHandler(host, { canUseWatchEvents: true, logger }, handleWatchEvents); + const session = createSessionWithCustomEventHandler({ host, canUseWatchEvents: true, logger }, handleWatchEvents); openFilesForSession(["/user/username/projects/myproject/a.ts"], session); // Directory watcher @@ -166,7 +165,7 @@ describe("unittests:: tsserver:: events:: watchEvents", () => { it("canUseWatchEvents without canUseEvents", () => { const { host, logger } = setup(); - const session = createSession(host, { canUseEvents: false, logger }); + const session = new TestSession({ host, canUseEvents: false, canUseWatchEvents: true, logger }); openFilesForSession(["/user/username/projects/myproject/a.ts"], session); // Directory watcher diff --git a/src/testRunner/unittests/tsserver/exportMapCache.ts b/src/testRunner/unittests/tsserver/exportMapCache.ts index 1a3063ce86029..e3f27e963701c 100644 --- a/src/testRunner/unittests/tsserver/exportMapCache.ts +++ b/src/testRunner/unittests/tsserver/exportMapCache.ts @@ -1,14 +1,12 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { jsonToReadableText, } from "../helpers"; import { baselineTsserverLogs, - createSession, + closeFilesForSession, openFilesForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -69,8 +67,8 @@ describe("unittests:: tsserver:: exportMapCache", () => { }); it("invalidates the cache when files are deleted", () => { - const { host, projectService, exportMapCache, session } = setup(); - projectService.closeClientFile(aTs.path); + const { host, exportMapCache, session } = setup(); + closeFilesForSession([aTs], session); host.deleteFile(aTs.path); host.runQueuedTimeoutCallbacks(); assert.ok(!exportMapCache.isUsableByFile(bTs.path as ts.Path)); @@ -162,8 +160,7 @@ describe("unittests:: tsserver:: exportMapCache", () => { }`, }; const host = createServerHost([utilsTs, classesTs, tsconfig]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); - const projectService = session.getProjectService(); + const session = new TestSession(host); openFilesForSession([classesTs], session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.Configure, @@ -187,7 +184,7 @@ describe("unittests:: tsserver:: exportMapCache", () => { }, }); - const project = projectService.configuredProjects.get(tsconfig.path)!; + const project = session.getProjectService().configuredProjects.get(tsconfig.path)!; const exportMapCache = project.getCachedExportInfoMap(); assert.ok(exportMapCache.isUsableByFile(classesTs.path as ts.Path)); assert.ok(!exportMapCache.isEmpty()); @@ -233,13 +230,12 @@ describe("unittests:: tsserver:: exportMapCache", () => { function setup() { const host = createServerHost([aTs, bTs, ambientDeclaration, tsconfig, packageJson, mobxPackageJson, mobxDts, exportEqualsMappedType]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([aTs, bTs], session); - const projectService = session.getProjectService(); - const project = projectService.configuredProjects.get(tsconfig.path)!; + const project = session.getProjectService().configuredProjects.get(tsconfig.path)!; triggerCompletions(); const checker = project.getLanguageService().getProgram()!.getTypeChecker(); - return { host, project, projectService, session, exportMapCache: project.getCachedExportInfoMap(), checker, triggerCompletions }; + return { host, project, session, exportMapCache: project.getCachedExportInfoMap(), checker, triggerCompletions }; function triggerCompletions() { const requestLocation: ts.server.protocol.FileLocationRequestArgs = { diff --git a/src/testRunner/unittests/tsserver/extends.ts b/src/testRunner/unittests/tsserver/extends.ts index cc3e4126876dd..d37b69aafbaf8 100644 --- a/src/testRunner/unittests/tsserver/extends.ts +++ b/src/testRunner/unittests/tsserver/extends.ts @@ -1,19 +1,16 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import { getSymlinkedExtendsSys, } from "../helpers/extends"; import { baselineTsserverLogs, - createSession, openFilesForSession, + TestSession, } from "../helpers/tsserver"; describe("unittests:: tsserver:: extends::", () => { it("resolves the symlink path", () => { const host = getSymlinkedExtendsSys(/*forTsserver*/ true); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession(["/users/user/projects/myproject/src/index.ts"], session); baselineTsserverLogs("tsserver", "resolves the symlink path", session); }); diff --git a/src/testRunner/unittests/tsserver/externalProjects.ts b/src/testRunner/unittests/tsserver/externalProjects.ts index d695621c184ae..5426f09a178ef 100644 --- a/src/testRunner/unittests/tsserver/externalProjects.ts +++ b/src/testRunner/unittests/tsserver/externalProjects.ts @@ -1,6 +1,3 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as Harness from "../../_namespaces/Harness"; import * as ts from "../../_namespaces/ts"; import { @@ -8,13 +5,13 @@ import { } from "../helpers"; import { baselineTsserverLogs, - createProjectService, - createSession, + closeFilesForSession, logConfiguredProjectsHasOpenRefStatus, logInferredProjectsOrphanStatus, openExternalProjectForSession, openExternalProjectsForSession, openFilesForSession, + TestSession, toExternalFile, toExternalFiles, verifyDynamic, @@ -40,7 +37,7 @@ describe("unittests:: tsserver:: externalProjects", () => { }; const host = createServerHost([f1, config], { useCaseSensitiveFileNames: false }); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.Configure, arguments: { @@ -100,7 +97,7 @@ describe("unittests:: tsserver:: externalProjects", () => { error: undefined, }; }; - const session = createSession(host, { globalPlugins: ["myplugin"], logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession({ host, globalPlugins: ["myplugin"] }); openExternalProjectsForSession([p1], session); session.executeCommandSeq({ @@ -132,7 +129,7 @@ describe("unittests:: tsserver:: externalProjects", () => { const p3 = makeProject(f3); const host = createServerHost([f1, f2, f3]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openExternalProjectsForSession([p1, p2], session); openExternalProjectsForSession([p1, p3], session); openExternalProjectsForSession([], session); @@ -149,38 +146,46 @@ describe("unittests:: tsserver:: externalProjects", () => { path: "/a/b/f2.ts", content: "let y =1;", }; - const externalProjectName = "externalproject"; + const projectFileName = "externalproject"; const host = createServerHost([file1, file2]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openExternalProject({ + const session = new TestSession(host); + openExternalProjectForSession({ rootFiles: toExternalFiles([file1.path, file2.path]), options: {}, - projectFileName: externalProjectName, - }); + projectFileName, + }, session); // open client file - should not lead to creation of inferred project - projectService.openClientFile(file1.path, file1.content); + openFilesForSession([file1], session); // close client file - external project should still exists - projectService.closeClientFile(file1.path); - projectService.closeExternalProject(externalProjectName); - baselineTsserverLogs("externalProjects", "should not close external project with no open files", projectService); + closeFilesForSession([file1], session); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.CloseExternalProject, + arguments: { projectFileName }, + }); + baselineTsserverLogs("externalProjects", "should not close external project with no open files", session); }); it("external project for dynamic file", () => { - const externalProjectName = "^ScriptDocument1 file1.ts"; + const projectFileName = "^ScriptDocument1 file1.ts"; const externalFiles = toExternalFiles(["^ScriptDocument1 file1.ts"]); const host = createServerHost([]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openExternalProject({ + const session = new TestSession(host); + openExternalProjectForSession({ rootFiles: externalFiles, options: {}, - projectFileName: externalProjectName, - }); + projectFileName, + }, session); - verifyDynamic(projectService, "/^scriptdocument1 file1.ts"); + verifyDynamic(session, "/^scriptdocument1 file1.ts"); externalFiles[0].content = "let x =1;"; - projectService.applyChangesInOpenFiles(externalFiles); - baselineTsserverLogs("externalProjects", "external project for dynamic file", projectService); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.ApplyChangedToOpenFiles, + arguments: { + openFiles: externalFiles, + }, + }); + baselineTsserverLogs("externalProjects", "external project for dynamic file", session); }); it("when file name starts with ^", () => { @@ -193,16 +198,16 @@ describe("unittests:: tsserver:: externalProjects", () => { content: "const y = 10;", }; const host = createServerHost([file, app, libFile]); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - service.openExternalProjects([{ + const session = new TestSession(host); + openExternalProjectsForSession([{ projectFileName: `/user/username/projects/myproject/myproject.njsproj`, rootFiles: [ toExternalFile(file.path), toExternalFile(app.path), ], options: {}, - }]); - baselineTsserverLogs("externalProjects", "when file name starts with caret", service); + }], session); + baselineTsserverLogs("externalProjects", "when file name starts with caret", session); }); it("external project that included config files", () => { @@ -236,34 +241,37 @@ describe("unittests:: tsserver:: externalProjects", () => { path: "/a/d/f3.ts", content: "let z =1;", }; - const externalProjectName = "externalproject"; + const projectFileName = "externalproject"; const host = createServerHost([file1, file2, file3, config1, config2]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openExternalProject({ + const session = new TestSession(host); + openExternalProjectForSession({ rootFiles: toExternalFiles([config1.path, config2.path, file3.path]), options: {}, - projectFileName: externalProjectName, - }); + projectFileName, + }, session); // open client file - should not lead to creation of inferred project - projectService.openClientFile(file1.path, file1.content); - logInferredProjectsOrphanStatus(projectService); + openFilesForSession([file1], session); + logInferredProjectsOrphanStatus(session); - projectService.openClientFile(file3.path, file3.content); - logInferredProjectsOrphanStatus(projectService); + openFilesForSession([file3], session); + logInferredProjectsOrphanStatus(session); - projectService.closeExternalProject(externalProjectName); - logInferredProjectsOrphanStatus(projectService); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.CloseExternalProject, + arguments: { projectFileName }, + }); + logInferredProjectsOrphanStatus(session); // open file 'file1' from configured project keeps project alive - projectService.closeClientFile(file3.path); - logInferredProjectsOrphanStatus(projectService); + closeFilesForSession([file3], session); + logInferredProjectsOrphanStatus(session); - projectService.closeClientFile(file1.path); - logInferredProjectsOrphanStatus(projectService); + closeFilesForSession([file1], session); + logInferredProjectsOrphanStatus(session); - projectService.openClientFile(file2.path, file2.content); - baselineTsserverLogs("externalProjects", "external project that included config files", projectService); + openFilesForSession([file2], session); + baselineTsserverLogs("externalProjects", "external project that included config files", session); }); it("external project with included config file opened after configured project", () => { @@ -275,23 +283,26 @@ describe("unittests:: tsserver:: externalProjects", () => { path: "/a/b/tsconfig.json", content: jsonToReadableText({ compilerOptions: {} }), }; - const externalProjectName = "externalproject"; + const projectFileName = "externalproject"; const host = createServerHost([file1, configFile]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); - projectService.openClientFile(file1.path); + openFilesForSession([file1], session); - projectService.openExternalProject({ + openExternalProjectForSession({ rootFiles: toExternalFiles([configFile.path]), options: {}, - projectFileName: externalProjectName, - }); + projectFileName, + }, session); - projectService.closeClientFile(file1.path); + closeFilesForSession([file1], session); // configured project is alive since it is opened as part of external project - projectService.closeExternalProject(externalProjectName); - baselineTsserverLogs("externalProjects", "external project with included config file opened after configured project", projectService); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.CloseExternalProject, + arguments: { projectFileName }, + }); + baselineTsserverLogs("externalProjects", "external project with included config file opened after configured project", session); }); it("external project with included config file opened after configured project and then closed", () => { @@ -307,25 +318,28 @@ describe("unittests:: tsserver:: externalProjects", () => { path: "/a/b/tsconfig.json", content: jsonToReadableText({ compilerOptions: {} }), }; - const externalProjectName = "externalproject"; + const projectFileName = "externalproject"; const host = createServerHost([file1, file2, libFile, configFile]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); - projectService.openClientFile(file1.path); + openFilesForSession([file1], session); - projectService.openExternalProject({ + openExternalProjectForSession({ rootFiles: toExternalFiles([configFile.path]), options: {}, - projectFileName: externalProjectName, - }); + projectFileName, + }, session); - projectService.closeExternalProject(externalProjectName); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.CloseExternalProject, + arguments: { projectFileName }, + }); // configured project is alive since file is still open - projectService.closeClientFile(file1.path); + closeFilesForSession([file1], session); - projectService.openClientFile(file2.path); - baselineTsserverLogs("externalProjects", "external project with included config file opened after configured project and then closed", projectService); + openFilesForSession([file2], session); + baselineTsserverLogs("externalProjects", "external project with included config file opened after configured project and then closed", session); }); it("can correctly update external project when set of root files has changed", () => { @@ -338,12 +352,20 @@ describe("unittests:: tsserver:: externalProjects", () => { content: "let y = 1", }; const host = createServerHost([file1, file2]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path]) }); + openExternalProjectForSession({ + projectFileName: "project", + options: {}, + rootFiles: toExternalFiles([file1.path]), + }, session); - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]) }); - baselineTsserverLogs("externalProjects", "can correctly update external project when set of root files has changed", projectService); + openExternalProjectForSession({ + projectFileName: "project", + options: {}, + rootFiles: toExternalFiles([file1.path, file2.path]), + }, session); + baselineTsserverLogs("externalProjects", "can correctly update external project when set of root files has changed", session); }); it("can update external project when set of root files was not changed", () => { @@ -361,12 +383,20 @@ describe("unittests:: tsserver:: externalProjects", () => { }; const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); - projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ts.ModuleResolutionKind.Node10 }, rootFiles: toExternalFiles([file1.path, file2.path]) }); + openExternalProjectForSession({ + projectFileName: "project", + options: { moduleResolution: ts.ModuleResolutionKind.Node10 }, + rootFiles: toExternalFiles([file1.path, file2.path]), + }, session); - projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ts.ModuleResolutionKind.Classic }, rootFiles: toExternalFiles([file1.path, file2.path]) }); - baselineTsserverLogs("externalProjects", "can update external project when set of root files was not changed", projectService); + openExternalProjectForSession({ + projectFileName: "project", + options: { moduleResolution: ts.ModuleResolutionKind.Classic }, + rootFiles: toExternalFiles([file1.path, file2.path]), + }, session); + baselineTsserverLogs("externalProjects", "can update external project when set of root files was not changed", session); }); it("language service disabled state is updated in external projects", () => { @@ -382,30 +412,30 @@ describe("unittests:: tsserver:: externalProjects", () => { const originalGetFileSize = host.getFileSize; host.getFileSize = (filePath: string) => filePath === f2.path ? ts.server.maxProgramSizeForNonTsFiles + 1 : originalGetFileSize.call(host, filePath); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); const projectFileName = "/a/proj.csproj"; - service.openExternalProject({ + openExternalProjectForSession({ projectFileName, rootFiles: toExternalFiles([f1.path, f2.path]), options: {}, - }); - assert.isFalse(service.externalProjects[0].languageServiceEnabled, "language service should be disabled - 1"); + }, session); + assert.isFalse(session.getProjectService().externalProjects[0].languageServiceEnabled, "language service should be disabled - 1"); - service.openExternalProject({ + openExternalProjectForSession({ projectFileName, rootFiles: toExternalFiles([f1.path]), options: {}, - }); - assert.isTrue(service.externalProjects[0].languageServiceEnabled, "language service should be enabled"); + }, session); + assert.isTrue(session.getProjectService().externalProjects[0].languageServiceEnabled, "language service should be enabled"); - service.openExternalProject({ + openExternalProjectForSession({ projectFileName, rootFiles: toExternalFiles([f1.path, f2.path]), options: {}, - }); - assert.isFalse(service.externalProjects[0].languageServiceEnabled, "language service should be disabled - 2"); - baselineTsserverLogs("externalProjects", "language service disabled state is updated in external projects", service); + }, session); + assert.isFalse(session.getProjectService().externalProjects[0].languageServiceEnabled, "language service should be disabled - 2"); + baselineTsserverLogs("externalProjects", "language service disabled state is updated in external projects", session); }); describe("deleting config file opened from the external project works", () => { @@ -420,8 +450,11 @@ describe("unittests:: tsserver:: externalProjects", () => { }; const projectFileName = "/user/someuser/project/WebApplication6.csproj"; const host = createServerHost([libFile, site, configFile]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); + const session = new TestSession(host); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.Configure, + arguments: { preferences: { lazyConfiguredProjectsFromExternalProject } }, + }); const externalProject: ts.server.protocol.ExternalProject = { projectFileName, @@ -430,18 +463,28 @@ describe("unittests:: tsserver:: externalProjects", () => { typeAcquisition: { include: [] }, }; - projectService.openExternalProjects([externalProject]); + openExternalProjectsForSession([externalProject], session); - const knownProjects = projectService.synchronizeProjectList([]); + const knownProjects = session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.SynchronizeProjectList, + arguments: { + knownProjects: [], + }, + }).response as ts.server.protocol.ProjectFilesWithDiagnostics[]; host.deleteFile(configFile.path); - projectService.synchronizeProjectList(ts.map(knownProjects, proj => proj.info!)); // TODO: GH#18217 GH#20039 + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.SynchronizeProjectList, + arguments: { + knownProjects: knownProjects.map(p => p.info!), + }, + }).response as ts.server.protocol.ProjectFilesWithDiagnostics[]; externalProject.rootFiles.length = 1; - projectService.openExternalProjects([externalProject]); + openExternalProjectsForSession([externalProject], session); - baselineTsserverLogs("externalProjects", `deleting config file opened from the external project works${lazyConfiguredProjectsFromExternalProject ? " with lazyConfiguredProjectsFromExternalProject" : ""}`, projectService); + baselineTsserverLogs("externalProjects", `deleting config file opened from the external project works${lazyConfiguredProjectsFromExternalProject ? " with lazyConfiguredProjectsFromExternalProject" : ""}`, session); } it("when lazyConfiguredProjectsFromExternalProject not set", () => { verifyDeletingConfigFile(/*lazyConfiguredProjectsFromExternalProject*/ false); @@ -466,37 +509,40 @@ describe("unittests:: tsserver:: externalProjects", () => { content: "", }; const host = createServerHost([f1, f2]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); + const session = new TestSession(host); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.Configure, + arguments: { preferences: { lazyConfiguredProjectsFromExternalProject } }, + }); // open external project - const projectName = "/a/b/proj1"; - projectService.openExternalProject({ - projectFileName: projectName, + const projectFileName = "/a/b/proj1"; + openExternalProjectForSession({ + projectFileName, rootFiles: toExternalFiles([f1.path, f2.path]), options: {}, - }); - projectService.openClientFile(f1.path); + }, session); + openFilesForSession([f1], session); // rename lib.ts to tsconfig.json host.renameFile(f2.path, tsconfig.path); - projectService.openExternalProject({ - projectFileName: projectName, + openExternalProjectForSession({ + projectFileName, rootFiles: toExternalFiles([f1.path, tsconfig.path]), options: {}, - }); + }, session); if (lazyConfiguredProjectsFromExternalProject) { - projectService.ensureInferredProjectsUpToDate_TestOnly(); + session.getProjectService().ensureInferredProjectsUpToDate_TestOnly(); } // rename tsconfig.json back to lib.ts host.renameFile(tsconfig.path, f2.path); - projectService.openExternalProject({ - projectFileName: projectName, + openExternalProjectForSession({ + projectFileName, rootFiles: toExternalFiles([f1.path, f2.path]), options: {}, - }); - baselineTsserverLogs("externalProjects", `correctly handling add or remove tsconfig - 1${lazyConfiguredProjectsFromExternalProject ? " with lazyConfiguredProjectsFromExternalProject" : ""}`, projectService); + }, session); + baselineTsserverLogs("externalProjects", `correctly handling add or remove tsconfig - 1${lazyConfiguredProjectsFromExternalProject ? " with lazyConfiguredProjectsFromExternalProject" : ""}`, session); } it("when lazyConfiguredProjectsFromExternalProject not set", () => { verifyAddRemoveConfig(/*lazyConfiguredProjectsFromExternalProject*/ false); @@ -529,55 +575,61 @@ describe("unittests:: tsserver:: externalProjects", () => { content: "{}", }; const host = createServerHost([f1, cLib, cTsconfig, dLib, dTsconfig]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject } }); + const session = new TestSession(host); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.Configure, + arguments: { preferences: { lazyConfiguredProjectsFromExternalProject } }, + }); // open external project - const projectName = "/a/b/proj1"; - projectService.openExternalProject({ - projectFileName: projectName, + const projectFileName = "/a/b/proj1"; + openExternalProjectForSession({ + projectFileName, rootFiles: toExternalFiles([f1.path]), options: {}, - }); + }, session); // add two config file as root files - projectService.openExternalProject({ - projectFileName: projectName, + openExternalProjectForSession({ + projectFileName, rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), options: {}, - }); + }, session); if (lazyConfiguredProjectsFromExternalProject) { - projectService.ensureInferredProjectsUpToDate_TestOnly(); + session.getProjectService().ensureInferredProjectsUpToDate_TestOnly(); } // remove one config file - projectService.openExternalProject({ - projectFileName: projectName, + openExternalProjectForSession({ + projectFileName, rootFiles: toExternalFiles([f1.path, dTsconfig.path]), options: {}, - }); + }, session); // remove second config file - projectService.openExternalProject({ - projectFileName: projectName, + openExternalProjectForSession({ + projectFileName, rootFiles: toExternalFiles([f1.path]), options: {}, - }); + }, session); // open two config files // add two config file as root files - projectService.openExternalProject({ - projectFileName: projectName, + openExternalProjectForSession({ + projectFileName, rootFiles: toExternalFiles([f1.path, cTsconfig.path, dTsconfig.path]), options: {}, - }); + }, session); if (lazyConfiguredProjectsFromExternalProject) { - projectService.ensureInferredProjectsUpToDate_TestOnly(); + session.getProjectService().ensureInferredProjectsUpToDate_TestOnly(); } // close all projects - no projects should be opened - projectService.closeExternalProject(projectName); - baselineTsserverLogs("externalProjects", `correctly handling add or remove tsconfig - 2${lazyConfiguredProjectsFromExternalProject ? " with lazyConfiguredProjectsFromExternalProject" : ""}`, projectService); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.CloseExternalProject, + arguments: { projectFileName }, + }); + baselineTsserverLogs("externalProjects", `correctly handling add or remove tsconfig - 2${lazyConfiguredProjectsFromExternalProject ? " with lazyConfiguredProjectsFromExternalProject" : ""}`, session); } it("when lazyConfiguredProjectsFromExternalProject not set", () => { @@ -635,13 +687,13 @@ describe("unittests:: tsserver:: externalProjects", () => { ), }; const host = createServerHost([libES5, libES2015Promise, app, config1], { executingFilePath: "/compiler/tsc.js" }); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(app.path); + const session = new TestSession(host); + openFilesForSession([app], session); host.writeFile(config2.path, config2.content); host.runQueuedTimeoutCallbacks(); - baselineTsserverLogs("externalProjects", "correctly handles changes in lib section of config file", projectService); + baselineTsserverLogs("externalProjects", "correctly handles changes in lib section of config file", session); }); it("should handle non-existing directories in config file", () => { @@ -660,15 +712,15 @@ describe("unittests:: tsserver:: externalProjects", () => { }), }; const host = createServerHost([f, config]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(f.path); - logConfiguredProjectsHasOpenRefStatus(projectService); - projectService.closeClientFile(f.path); - logConfiguredProjectsHasOpenRefStatus(projectService); - - projectService.openClientFile(f.path); - logConfiguredProjectsHasOpenRefStatus(projectService); - baselineTsserverLogs("externalProjects", "should handle non-existing directories in config file", projectService); + const session = new TestSession(host); + openFilesForSession([f], session); + logConfiguredProjectsHasOpenRefStatus(session); + closeFilesForSession([f], session); + logConfiguredProjectsHasOpenRefStatus(session); + + openFilesForSession([f], session); + logConfiguredProjectsHasOpenRefStatus(session); + baselineTsserverLogs("externalProjects", "should handle non-existing directories in config file", session); }); it("handles loads existing configured projects of external projects when lazyConfiguredProjectsFromExternalProject is disabled", () => { @@ -682,29 +734,38 @@ describe("unittests:: tsserver:: externalProjects", () => { }; const projectFileName = "/a/b/project.csproj"; const host = createServerHost([f1, config]); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: true } }); - service.openExternalProject({ + const session = new TestSession(host); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.Configure, + arguments: { preferences: { lazyConfiguredProjectsFromExternalProject: true } }, + }); + openExternalProjectForSession({ projectFileName, rootFiles: toExternalFiles([f1.path, config.path]), options: {}, - } as ts.server.protocol.ExternalProject); - const project = service.configuredProjects.get(config.path)!; + }, session); + const project = session.getProjectService().configuredProjects.get(config.path)!; assert.equal(project.pendingUpdateLevel, ts.ProgramUpdateLevel.Full); // External project referenced configured project pending to be reloaded - service.setHostConfiguration({ preferences: { lazyConfiguredProjectsFromExternalProject: false } }); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.Configure, + arguments: { preferences: { lazyConfiguredProjectsFromExternalProject: false } }, + }); assert.equal(project.pendingUpdateLevel, ts.ProgramUpdateLevel.Update); // External project referenced configured project loaded - service.closeExternalProject(projectFileName); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.CloseExternalProject, + arguments: { projectFileName }, + }); - service.openExternalProject({ + openExternalProjectForSession({ projectFileName, rootFiles: toExternalFiles([f1.path, config.path]), options: {}, - } as ts.server.protocol.ExternalProject); - const project2 = service.configuredProjects.get(config.path)!; + }, session); + const project2 = session.getProjectService().configuredProjects.get(config.path)!; assert.equal(project2.pendingUpdateLevel, ts.ProgramUpdateLevel.Update); // External project referenced configured project loaded - baselineTsserverLogs("externalProjects", "handles loads existing configured projects of external projects when lazyConfiguredProjectsFromExternalProject is disabled", service); + baselineTsserverLogs("externalProjects", "handles loads existing configured projects of external projects when lazyConfiguredProjectsFromExternalProject is disabled", session); }); it("handles creation of external project with jsconfig before jsconfig creation watcher is invoked", () => { @@ -715,24 +776,29 @@ describe("unittests:: tsserver:: externalProjects", () => { }; const files = [libFile, tsconfig]; const host = createServerHost(files); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); // Create external project - service.openExternalProjects([{ + openExternalProjectsForSession([{ projectFileName, rootFiles: [{ fileName: tsconfig.path }], options: { allowJs: false }, - }]); + }], session); // write js file, open external project and open it for edit const jsFilePath = `/user/username/projects/myproject/javascript.js`; host.writeFile(jsFilePath, ""); - service.openExternalProjects([{ + openExternalProjectsForSession([{ projectFileName, rootFiles: [{ fileName: tsconfig.path }, { fileName: jsFilePath }], options: { allowJs: false }, - }]); - service.applyChangesInOpenFiles(ts.singleIterator({ fileName: jsFilePath, scriptKind: ts.ScriptKind.JS, content: "" })); + }], session); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.ApplyChangedToOpenFiles, + arguments: { + openFiles: [{ fileName: jsFilePath, scriptKind: "JS", content: "" }], + }, + }); // write jsconfig file const jsConfig: File = { @@ -743,13 +809,13 @@ describe("unittests:: tsserver:: externalProjects", () => { host.ensureFileOrFolder(jsConfig, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); // Open external project - service.openExternalProjects([{ + openExternalProjectsForSession([{ projectFileName, rootFiles: [{ fileName: jsConfig.path }, { fileName: tsconfig.path }, { fileName: jsFilePath }], options: { allowJs: false }, - }]); - logInferredProjectsOrphanStatus(service); - baselineTsserverLogs("externalProjects", "handles creation of external project with jsconfig before jsconfig creation watcher is invoked", service); + }], session); + logInferredProjectsOrphanStatus(session); + baselineTsserverLogs("externalProjects", "handles creation of external project with jsconfig before jsconfig creation watcher is invoked", session); }); it("does not crash if external file does not exist", () => { @@ -778,10 +844,7 @@ describe("unittests:: tsserver:: externalProjects", () => { error: undefined, }; }; - const session = createSession(host, { - globalPlugins: ["myplugin"], - logger: createLoggerWithInMemoryLogs(host), - }); + const session = new TestSession({ host, globalPlugins: ["myplugin"] }); // When the external project is opened, the graph will be updated, // and in the process getExternalFiles() above will be called. // Since the external file does not exist, there will not be a script diff --git a/src/testRunner/unittests/tsserver/findAllReferences.ts b/src/testRunner/unittests/tsserver/findAllReferences.ts index 928b429265181..485acd4c85f8b 100644 --- a/src/testRunner/unittests/tsserver/findAllReferences.ts +++ b/src/testRunner/unittests/tsserver/findAllReferences.ts @@ -1,12 +1,9 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import { protocol, } from "../../_namespaces/ts.server"; import { baselineTsserverLogs, - createSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -80,7 +77,7 @@ const bar: Bar = { }, ]; const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); // Open files in the two configured projects session.executeCommandSeq({ command: protocol.CommandTypes.UpdateOpen, diff --git a/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts b/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts index 16e24dfb6dbc2..7e264e856731f 100644 --- a/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts +++ b/src/testRunner/unittests/tsserver/forceConsistentCasingInFileNames.ts @@ -1,6 +1,3 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { jsonToReadableText, @@ -8,9 +5,9 @@ import { import { baselineTsserverLogs, closeFilesForSession, - createSession, openFilesForSession, protocolTextSpanFromSubstring, + TestSession, verifyGetErrRequest, } from "../helpers/tsserver"; import { @@ -51,7 +48,7 @@ describe("unittests:: tsserver:: forceConsistentCasingInFileNames", () => { }; const host = createServerHost([file1, file2, file2Dts, libFile, tsconfig, tsconfigAll], { useCaseSensitiveFileNames: false }); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1], session); session.executeCommandSeq({ @@ -80,7 +77,7 @@ describe("unittests:: tsserver:: forceConsistentCasingInFileNames", () => { }; const host = createServerHost([loggerFile, anotherFile, tsconfig, libFile, tsconfig]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([{ file: loggerFile, projectRootPath: "/user/username/projects/myproject" }], session); verifyGetErrRequest({ session, files: [loggerFile] }); @@ -129,7 +126,7 @@ describe("unittests:: tsserver:: forceConsistentCasingInFileNames", () => { }; const host = createServerHost([loggerFile, anotherFile, tsconfig, libFile, tsconfig]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([{ file: anotherFile, projectRootPath: "/user/username/projects/myproject" }], session); verifyGetErrRequest({ session, files: [anotherFile] }); diff --git a/src/testRunner/unittests/tsserver/formatSettings.ts b/src/testRunner/unittests/tsserver/formatSettings.ts index 0cd2c18536759..0415f416c4f0f 100644 --- a/src/testRunner/unittests/tsserver/formatSettings.ts +++ b/src/testRunner/unittests/tsserver/formatSettings.ts @@ -1,14 +1,11 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { jsonToReadableText, } from "../helpers"; import { baselineTsserverLogs, - createSession, openFilesForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -21,7 +18,7 @@ describe("unittests:: tsserver:: formatSettings", () => { content: "let x;", }; const host = createServerHost([f1]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([f1], session); const defaultSettings = session.getProjectService().getFormatCodeOptions(f1.path as ts.server.NormalizedPath); diff --git a/src/testRunner/unittests/tsserver/getApplicableRefactors.ts b/src/testRunner/unittests/tsserver/getApplicableRefactors.ts index 1c9cc8ee2495f..6ee8a48be6ce7 100644 --- a/src/testRunner/unittests/tsserver/getApplicableRefactors.ts +++ b/src/testRunner/unittests/tsserver/getApplicableRefactors.ts @@ -1,11 +1,8 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { baselineTsserverLogs, - createSession, openFilesForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -16,7 +13,7 @@ describe("unittests:: tsserver:: getApplicableRefactors", () => { it("works when taking position", () => { const aTs: File = { path: "/a.ts", content: "" }; const host = createServerHost([aTs]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([aTs], session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.GetApplicableRefactors, diff --git a/src/testRunner/unittests/tsserver/getEditsForFileRename.ts b/src/testRunner/unittests/tsserver/getEditsForFileRename.ts index 2c8cc169642f8..463670439867a 100644 --- a/src/testRunner/unittests/tsserver/getEditsForFileRename.ts +++ b/src/testRunner/unittests/tsserver/getEditsForFileRename.ts @@ -1,14 +1,11 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { jsonToReadableText, } from "../helpers"; import { baselineTsserverLogs, - createSession, openFilesForSession, + TestSession, textSpanFromSubstring, } from "../helpers/tsserver"; import { @@ -83,7 +80,7 @@ describe("unittests:: tsserver:: getEditsForFileRename", () => { }; const host = createServerHost([aUserTs, aOldTs, aTsconfig, bUserTs, bTsconfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([aUserTs, bUserTs], session); session.executeCommandSeq({ @@ -102,7 +99,7 @@ describe("unittests:: tsserver:: getEditsForFileRename", () => { const tsconfig: File = { path: "/tsconfig.json", content: jsonToReadableText({ files: ["./a.ts", "./b.ts"] }) }; const host = createServerHost([aTs, cTs, tsconfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([aTs, cTs], session); session.executeCommandSeq({ diff --git a/src/testRunner/unittests/tsserver/getExportReferences.ts b/src/testRunner/unittests/tsserver/getExportReferences.ts index 31dd0e0c1f947..00ba5750c5bf8 100644 --- a/src/testRunner/unittests/tsserver/getExportReferences.ts +++ b/src/testRunner/unittests/tsserver/getExportReferences.ts @@ -1,12 +1,9 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { baselineTsserverLogs, - createSession, openFilesForSession, protocolFileLocationFromSubstring, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -32,7 +29,7 @@ export const { nest: [valueE, { valueF }] } = { nest: [0, { valueF: 1 }] }; content: "{}", }; const host = createServerHost([mainTs, modTs, tsconfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([mainTs, modTs], session); return { session, modTs }; } diff --git a/src/testRunner/unittests/tsserver/getFileReferences.ts b/src/testRunner/unittests/tsserver/getFileReferences.ts index 28d0d5feb88c1..f83b8580a63e3 100644 --- a/src/testRunner/unittests/tsserver/getFileReferences.ts +++ b/src/testRunner/unittests/tsserver/getFileReferences.ts @@ -1,11 +1,8 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { baselineTsserverLogs, - createSession, openFilesForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -41,7 +38,7 @@ describe("unittests:: tsserver:: getFileReferences", () => { function makeSampleSession() { const host = createServerHost([aTs, bTs, cTs, dTs, tsconfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([aTs, bTs, cTs, dTs], session); return session; } diff --git a/src/testRunner/unittests/tsserver/getMoveToRefactoringFileSuggestions.ts b/src/testRunner/unittests/tsserver/getMoveToRefactoringFileSuggestions.ts index 402ebf9929ca1..967dd319181c3 100644 --- a/src/testRunner/unittests/tsserver/getMoveToRefactoringFileSuggestions.ts +++ b/src/testRunner/unittests/tsserver/getMoveToRefactoringFileSuggestions.ts @@ -1,14 +1,11 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { jsonToReadableText, } from "../helpers"; import { baselineTsserverLogs, - createSession, openFilesForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -44,7 +41,7 @@ import { value1 } from "../node_modules/.cache/someFile.d.ts";`, content: "{}", }; const host = createServerHost([file1, file2, file3, file3, file4, nodeModulesFile1, nodeModulesFile2, tsconfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1], session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.GetMoveToRefactoringFileSuggestions, @@ -69,7 +66,7 @@ import { value1 } from "../node_modules/.cache/someFile.d.ts";`, const tsconfig: File = { path: "/tsconfig.json", content: jsonToReadableText({ files: ["./file1.ts", "./file2.tsx", "./file3.mts", "./file4.cts", "./file5.js", "./file6.d.ts", "./file7.ts"] }) }; const host = createServerHost([file1, file2, file3, file4, file5, file6, file7, tsconfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1], session); session.executeCommandSeq({ @@ -90,7 +87,7 @@ import { value1 } from "../node_modules/.cache/someFile.d.ts";`, const tsconfig: File = { path: "/tsconfig.json", content: jsonToReadableText({ files: ["./file1.js", "./file2.js", "./file3.mts", "./file4.ts", "./file5.js"] }) }; const host = createServerHost([file1, file2, file3, file4, file5, tsconfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1], session); session.executeCommandSeq({ @@ -110,7 +107,7 @@ import { value1 } from "../node_modules/.cache/someFile.d.ts";`, const tsconfig: File = { path: "/tsconfig.json", content: jsonToReadableText({ files: ["./file1.d.ts", "./a/lib.d.ts", "./a/file3.d.ts", "/a/lib.es6.d.ts"] }) }; const host = createServerHost([file1, file2, file3, file4, tsconfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1], session); session.executeCommandSeq({ diff --git a/src/testRunner/unittests/tsserver/goToDefinition.ts b/src/testRunner/unittests/tsserver/goToDefinition.ts index 8b2cce8d61070..09d147d1577dc 100644 --- a/src/testRunner/unittests/tsserver/goToDefinition.ts +++ b/src/testRunner/unittests/tsserver/goToDefinition.ts @@ -1,12 +1,9 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import { protocol, } from "../../_namespaces/ts.server"; import { baselineTsserverLogs, - createSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -45,7 +42,7 @@ declare class Stuff { }, ]; const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); // Open files in the two configured projects session.executeCommandSeq({ command: protocol.CommandTypes.UpdateOpen, @@ -112,7 +109,7 @@ declare class Stuff { }, ]; const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); // Open files in the two configured projects session.executeCommandSeq({ command: protocol.CommandTypes.UpdateOpen, diff --git a/src/testRunner/unittests/tsserver/importHelpers.ts b/src/testRunner/unittests/tsserver/importHelpers.ts index 9b02e52a5a6d1..2d340b4a9f1e3 100644 --- a/src/testRunner/unittests/tsserver/importHelpers.ts +++ b/src/testRunner/unittests/tsserver/importHelpers.ts @@ -1,10 +1,7 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import { baselineTsserverLogs, - createSession, openExternalProjectForSession, + TestSession, toExternalFile, } from "../helpers/tsserver"; import { @@ -22,8 +19,12 @@ describe("unittests:: tsserver:: importHelpers", () => { content: "", }; const host = createServerHost([f1, tslib]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openExternalProjectForSession({ projectFileName: "p", rootFiles: [toExternalFile(f1.path)], options: { importHelpers: true } }, session); + const session = new TestSession(host); + openExternalProjectForSession({ + projectFileName: "p", + rootFiles: [toExternalFile(f1.path)], + options: { importHelpers: true }, + }, session); baselineTsserverLogs("importHelpers", "should not crash in tsserver", session); }); }); diff --git a/src/testRunner/unittests/tsserver/inconsistentErrorInEditor.ts b/src/testRunner/unittests/tsserver/inconsistentErrorInEditor.ts index da83c51c21fb2..718ade5a3749d 100644 --- a/src/testRunner/unittests/tsserver/inconsistentErrorInEditor.ts +++ b/src/testRunner/unittests/tsserver/inconsistentErrorInEditor.ts @@ -1,10 +1,7 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { baselineTsserverLogs, - createSession, + TestSession, verifyGetErrRequest, } from "../helpers/tsserver"; import { @@ -13,7 +10,7 @@ import { describe("unittests:: tsserver:: inconsistentErrorInEditor", () => { it("should not error", () => { const host = createServerHost([]); - const session = createSession(host, { canUseEvents: true, noGetErrOnBackgroundUpdate: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.UpdateOpen, arguments: { @@ -45,7 +42,7 @@ describe("unittests:: tsserver:: inconsistentErrorInEditor", () => { describe("unittests:: tsserver:: inconsistentErrorInEditor2", () => { it("should not error", () => { const host = createServerHost([]); - const session = createSession(host, { canUseEvents: true, noGetErrOnBackgroundUpdate: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.UpdateOpen, arguments: { diff --git a/src/testRunner/unittests/tsserver/inferredProjects.ts b/src/testRunner/unittests/tsserver/inferredProjects.ts index 5b3c16aa00196..01789f239e828 100644 --- a/src/testRunner/unittests/tsserver/inferredProjects.ts +++ b/src/testRunner/unittests/tsserver/inferredProjects.ts @@ -1,6 +1,3 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { dedent, @@ -14,11 +11,10 @@ import { import { baselineTsserverLogs, closeFilesForSession, - createProjectService, - createSession, logInferredProjectsOrphanStatus, openFilesForSession, setCompilerOptionsForInferredProjectsRequestForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -41,9 +37,9 @@ describe("unittests:: tsserver:: inferredProjects", () => { content: `export let x: number`, }; const host = createServerHost([appFile, moduleFile, libFile]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(appFile.path); - baselineTsserverLogs("inferredProjects", "create inferred project", projectService); + const session = new TestSession(host); + openFilesForSession([appFile], session); + baselineTsserverLogs("inferredProjects", "create inferred project", session); }); it("should use only one inferred project if 'useOneInferredProject' is set", () => { @@ -71,14 +67,12 @@ describe("unittests:: tsserver:: inferredProjects", () => { }; const host = createServerHost([file1, file2, file3, libFile]); - const projectService = createProjectService(host, { useSingleInferredProject: true, logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(file1.path); - projectService.openClientFile(file2.path); - projectService.openClientFile(file3.path); + const session = new TestSession({ host, useSingleInferredProject: true }); + openFilesForSession([file1, file2, file3], session); host.writeFile(configFile.path, configFile.content); host.runQueuedTimeoutCallbacks(); // load configured project from disk + ensureProjectsForOpenFiles - baselineTsserverLogs("inferredProjects", "should use only one inferred project if useOneInferredProject is set", projectService); + baselineTsserverLogs("inferredProjects", "should use only one inferred project if useOneInferredProject is set", session); }); it("disable inferred project", () => { @@ -88,12 +82,12 @@ describe("unittests:: tsserver:: inferredProjects", () => { }; const host = createServerHost([file1]); - const projectService = createProjectService(host, { useSingleInferredProject: true, serverMode: ts.LanguageServiceMode.Syntactic, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession({ host, useSingleInferredProject: true, serverMode: ts.LanguageServiceMode.Syntactic }); - projectService.openClientFile(file1.path, file1.content); + openFilesForSession([file1], session); - projectService.logger.log(`LanguageServiceEnabled:: ${projectService.inferredProjects[0].languageServiceEnabled}`); - baselineTsserverLogs("inferredProjects", "disable inferred project", projectService); + session.logger.log(`LanguageServiceEnabled:: ${session.getProjectService().inferredProjects[0].languageServiceEnabled}`); + baselineTsserverLogs("inferredProjects", "disable inferred project", session); }); it("project settings for inferred projects", () => { @@ -106,14 +100,15 @@ describe("unittests:: tsserver:: inferredProjects", () => { content: "export let x: number", }; const host = createServerHost([file1, modFile]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); - projectService.openClientFile(file1.path); - projectService.openClientFile(modFile.path); - projectService.setCompilerOptionsForInferredProjects({ moduleResolution: ts.ModuleResolutionKind.Classic }); + openFilesForSession([file1, modFile], session); + setCompilerOptionsForInferredProjectsRequestForSession({ + moduleResolution: ts.server.protocol.ModuleResolutionKind.Classic, + }, session); host.runQueuedTimeoutCallbacks(); - logInferredProjectsOrphanStatus(projectService); - baselineTsserverLogs("inferredProjects", "project settings for inferred projects", projectService); + logInferredProjectsOrphanStatus(session); + baselineTsserverLogs("inferredProjects", "project settings for inferred projects", session); }); it("should support files without extensions", () => { @@ -122,7 +117,7 @@ describe("unittests:: tsserver:: inferredProjects", () => { content: "let x = 1", }; const host = createServerHost([f]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); setCompilerOptionsForInferredProjectsRequestForSession({ allowJs: true }, session); openFilesForSession([{ file: f.path, content: f.content, scriptKindName: "JS" }], session); baselineTsserverLogs("inferredProjects", "should support files without extensions", session); @@ -134,19 +129,19 @@ describe("unittests:: tsserver:: inferredProjects", () => { const file3 = { path: "/b/file2.ts", content: "let x = 3;", projectRootPath: "/b" }; const file4 = { path: "/c/file3.ts", content: "let z = 4;" }; const host = createServerHost([file1, file2, file3, file4]); - const session = createSession(host, { + const session = new TestSession({ + host, useSingleInferredProject: true, useInferredProjectPerProjectRoot: true, - logger: createLoggerWithInMemoryLogs(host), }); setCompilerOptionsForInferredProjectsRequestForSession({ allowJs: true, - target: ts.ScriptTarget.ESNext, + target: ts.server.protocol.ScriptTarget.ESNext, }, session); setCompilerOptionsForInferredProjectsRequestForSession({ options: { allowJs: true, - target: ts.ScriptTarget.ES2015, + target: ts.server.protocol.ScriptTarget.ES2015, }, projectRootPath: "/b", }, session); @@ -190,15 +185,15 @@ describe("unittests:: tsserver:: inferredProjects", () => { { path: "/c/file3.ts", content: "let z = 4;" }, ]; const host = createServerHost(files, { useCaseSensitiveFileNames }); - const session = createSession(host, { useSingleInferredProject: true, useInferredProjectPerProjectRoot: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession({ host, useSingleInferredProject: true, useInferredProjectPerProjectRoot: true }); setCompilerOptionsForInferredProjectsRequestForSession({ allowJs: true, - target: ts.ScriptTarget.ESNext, + target: ts.server.protocol.ScriptTarget.ESNext, }, session); setCompilerOptionsForInferredProjectsRequestForSession({ options: { allowJs: true, - target: ts.ScriptTarget.ES2015, + target: ts.server.protocol.ScriptTarget.ES2015, }, projectRootPath: "/a", }, session); @@ -212,7 +207,7 @@ describe("unittests:: tsserver:: inferredProjects", () => { setCompilerOptionsForInferredProjectsRequestForSession({ options: { allowJs: true, - target: ts.ScriptTarget.ES2017, + target: ts.server.protocol.ScriptTarget.ES2017, }, projectRootPath: "/A", }, session); @@ -255,7 +250,7 @@ describe("unittests:: tsserver:: inferredProjects", () => { content: `const jsFile2 = 10;`, }; const host = createServerHost([appFile, libFile, config, jsFile1, jsFile2]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); // Do not remove config project when opening jsFile that is not present as part of config project openFilesForSession([jsFile1], session); @@ -276,25 +271,24 @@ describe("unittests:: tsserver:: inferredProjects", () => { const file1 = { path: "/a/file1.js", content: "" }; const host = createServerHost([file1]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); - projectService.openClientFile(file1.path); + openFilesForSession([file1], session); - const inferredProject = projectService.inferredProjects[0]; - projectService.logger.log(`typeAcquisition : setting to undefined`); + const inferredProject = session.getProjectService().inferredProjects[0]; + session.logger.log(`typeAcquisition : setting to undefined`); inferredProject.setTypeAcquisition(undefined); - projectService.logger.log(`typeAcquisition should be inferred for inferred projects: ${jsonToReadableText(inferredProject.getTypeAcquisition())}`); - baselineTsserverLogs("inferredProjects", "regression test - should infer typeAcquisition for inferred projects when set undefined", projectService); + session.logger.log(`typeAcquisition should be inferred for inferred projects: ${jsonToReadableText(inferredProject.getTypeAcquisition())}`); + baselineTsserverLogs("inferredProjects", "regression test - should infer typeAcquisition for inferred projects when set undefined", session); }); it("Setting compiler options for inferred projects when there are no open files should not schedule any refresh", () => { const host = createServerHost([commonFile1, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); setCompilerOptionsForInferredProjectsRequestForSession({ allowJs: true, - target: ts.ScriptTarget.ES2015, + target: ts.server.protocol.ScriptTarget.ES2015, }, session); - session.testhost.logTimeoutQueueLength(); baselineTsserverLogs("inferredProjects", "Setting compiler options for inferred projects when there are no open files should not schedule any refresh", session); }); @@ -322,7 +316,7 @@ describe("unittests:: tsserver:: inferredProjects", () => { `, [libFile.path]: libFile.content, }); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([{ file: "/user/username/projects/myproject/app.ts", projectRootPath: "/user/username/projects/myproject", diff --git a/src/testRunner/unittests/tsserver/inlayHints.ts b/src/testRunner/unittests/tsserver/inlayHints.ts index b92df9d2ae141..98c5a4b78b81a 100644 --- a/src/testRunner/unittests/tsserver/inlayHints.ts +++ b/src/testRunner/unittests/tsserver/inlayHints.ts @@ -1,6 +1,3 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { commonFile1, @@ -8,7 +5,6 @@ import { } from "../helpers/tscWatch"; import { baselineTsserverLogs, - createSession, TestSession, } from "../helpers/tsserver"; import { @@ -29,7 +25,7 @@ describe("unittests:: tsserver:: inlayHints", () => { it("with updateOpen request does not corrupt documents", () => { const host = createServerHost([app, commonFile1, commonFile2, libFile, configFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.Open, arguments: { file: app.path }, diff --git a/src/testRunner/unittests/tsserver/jsdocTag.ts b/src/testRunner/unittests/tsserver/jsdocTag.ts index ffd7e83e12dfd..ea05b360783f2 100644 --- a/src/testRunner/unittests/tsserver/jsdocTag.ts +++ b/src/testRunner/unittests/tsserver/jsdocTag.ts @@ -1,11 +1,8 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { baselineTsserverLogs, - createSession, openFilesForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -31,7 +28,7 @@ describe("unittests:: tsserver:: jsdocTag:: jsdoc @link ", () => { it(subScenario, () => { const { command, displayPartsForJSDoc } = options; const host = createServerHost([file, config]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.Configure, arguments: { preferences: { displayPartsForJSDoc } }, @@ -118,7 +115,7 @@ x(1)`, const { command, displayPartsForJSDoc } = options; const host = createServerHost([linkInParamTag, config]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.Configure, arguments: { preferences: { displayPartsForJSDoc } }, @@ -171,7 +168,7 @@ foo`, }; const { command, displayPartsForJSDoc } = options; const host = createServerHost([linkInParamJSDoc, config]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.Configure, arguments: { preferences: { displayPartsForJSDoc } }, diff --git a/src/testRunner/unittests/tsserver/languageService.ts b/src/testRunner/unittests/tsserver/languageService.ts index 06278415d20ab..7d4c779e08974 100644 --- a/src/testRunner/unittests/tsserver/languageService.ts +++ b/src/testRunner/unittests/tsserver/languageService.ts @@ -1,11 +1,9 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as Utils from "../../_namespaces/Utils"; import { baselineTsserverLogs, - createProjectService, logDiagnostics, + openFilesForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -22,10 +20,9 @@ describe("unittests:: tsserver:: languageService", () => { content: "let x = 1;", }; const host = createServerHost([lib, f], { executingFilePath: "/a/Lib/tsc.js", useCaseSensitiveFileNames: true }); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(f.path); - projectService.inferredProjects[0].getLanguageService().getProgram(); - baselineTsserverLogs("languageService", "should work correctly on case-sensitive file systems", projectService); + const session = new TestSession(host); + openFilesForSession([f], session); + baselineTsserverLogs("languageService", "should work correctly on case-sensitive file systems", session); }); it("should support multiple projects with the same file under differing `paths` settings", () => { @@ -67,21 +64,20 @@ describe("unittests:: tsserver:: languageService", () => { ]; const host = createServerHost(files, { executingFilePath: "/project/tsc.js", useCaseSensitiveFileNames: true }); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(files[3].path); - projectService.openClientFile(files[6].path); + const session = new TestSession(host); + openFilesForSession([files[3], files[6].path], session); logDiagnostics( - projectService, + session, `getSemanticDiagnostics:: ${files[1].path}`, - projectService.configuredProjects.get(files[1].path)!, - projectService.configuredProjects.get(files[1].path)!.getLanguageService().getProgram()!.getSemanticDiagnostics(), + session.getProjectService().configuredProjects.get(files[1].path)!, + session.getProjectService().configuredProjects.get(files[1].path)!.getLanguageService().getProgram()!.getSemanticDiagnostics(), ); logDiagnostics( - projectService, + session, `getSemanticDiagnostics:: ${files[4].path}`, - projectService.configuredProjects.get(files[4].path)!, - projectService.configuredProjects.get(files[4].path)!.getLanguageService().getProgram()!.getSemanticDiagnostics(), + session.getProjectService().configuredProjects.get(files[4].path)!, + session.getProjectService().configuredProjects.get(files[4].path)!.getLanguageService().getProgram()!.getSemanticDiagnostics(), ); - baselineTsserverLogs("languageService", "should support multiple projects with the same file under differing paths settings", projectService); + baselineTsserverLogs("languageService", "should support multiple projects with the same file under differing paths settings", session); }); }); diff --git a/src/testRunner/unittests/tsserver/libraryResolution.ts b/src/testRunner/unittests/tsserver/libraryResolution.ts index a33433a1beee0..b9fbccbe31b0c 100644 --- a/src/testRunner/unittests/tsserver/libraryResolution.ts +++ b/src/testRunner/unittests/tsserver/libraryResolution.ts @@ -1,6 +1,3 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import { jsonToReadableText, } from "../helpers"; @@ -9,14 +6,14 @@ import { } from "../helpers/libraryResolution"; import { baselineTsserverLogs, - createSession, openFilesForSession, + TestSession, } from "../helpers/tsserver"; describe("unittests:: tsserver:: libraryResolution", () => { it("with config", () => { const host = getServerHostForLibResolution(); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession(["/home/src/projects/project1/index.ts"], session); host.ensureFileOrFolder({ path: "/home/src/projects/node_modules/@typescript/lib-dom/index.d.ts", content: "interface DOMInterface { }" }); host.runQueuedTimeoutCallbacks(); @@ -61,7 +58,7 @@ describe("unittests:: tsserver:: libraryResolution", () => { }); it("with config with redirection", () => { const host = getServerHostForLibResolution(/*libRedirection*/ true); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession(["/home/src/projects/project1/index.ts"], session); host.deleteFile("/home/src/projects/node_modules/@typescript/lib-dom/index.d.ts"); host.runQueuedTimeoutCallbacks(); diff --git a/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts b/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts index fb71c8009f90b..1bef59c14fb5e 100644 --- a/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts +++ b/src/testRunner/unittests/tsserver/maxNodeModuleJsDepth.ts @@ -1,6 +1,3 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { dedent, @@ -8,9 +5,9 @@ import { import { baselineTsserverLogs, closeFilesForSession, - createSession, openFilesForSession, setCompilerOptionsForInferredProjectsRequestForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -30,13 +27,13 @@ describe("unittests:: tsserver:: maxNodeModuleJsDepth for inferred projects", () }; const host = createServerHost([file1, moduleFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1], session); session.logger.log(`maxNodeModuleJsDepth: ${session.getProjectService().inferredProjects[0].getCompilationSettings().maxNodeModuleJsDepth}`); // Assert the option sticks - setCompilerOptionsForInferredProjectsRequestForSession({ target: ts.ScriptTarget.ES2016 }, session); + setCompilerOptionsForInferredProjectsRequestForSession({ target: ts.server.protocol.ScriptTarget.ES2016 }, session); session.logger.log(`maxNodeModuleJsDepth: ${session.getProjectService().inferredProjects[0].getCompilationSettings().maxNodeModuleJsDepth}`); baselineTsserverLogs("maxNodeModuleJsDepth", "should be set to 2 if the project has js root files", session); }); @@ -52,7 +49,7 @@ describe("unittests:: tsserver:: maxNodeModuleJsDepth for inferred projects", () }; const host = createServerHost([file1, file2, libFile]); - const session = createSession(host, { useSingleInferredProject: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession({ host, useSingleInferredProject: true }); openFilesForSession([file1], session); session.logger.log(`maxNodeModuleJsDepth: ${session.getProjectService().inferredProjects[0].getCompilationSettings().maxNodeModuleJsDepth}`); @@ -84,7 +81,7 @@ describe("unittests:: tsserver:: maxNodeModuleJsDepth for inferred projects", () `, [libFile.path]: libFile.content, }); - const session = createSession(host, { useSingleInferredProject: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession({ host, useSingleInferredProject: true }); openFilesForSession(["/user/username/projects/project1/src/file1.js"], session); baselineTsserverLogs("maxNodeModuleJsDepth", "handles resolutions when currentNodeModulesDepth changes when referencing file from another file", session); diff --git a/src/testRunner/unittests/tsserver/metadataInResponse.ts b/src/testRunner/unittests/tsserver/metadataInResponse.ts index 83df002ab4a94..b60521e3d49b6 100644 --- a/src/testRunner/unittests/tsserver/metadataInResponse.ts +++ b/src/testRunner/unittests/tsserver/metadataInResponse.ts @@ -1,6 +1,3 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as Harness from "../../_namespaces/Harness"; import * as ts from "../../_namespaces/ts"; import { @@ -8,8 +5,8 @@ import { } from "../helpers"; import { baselineTsserverLogs, - createSession, openFilesForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -58,7 +55,7 @@ describe("unittests:: tsserver:: with metadataInResponse::", () => { it("can pass through metadata when the command returns array", () => { const host = createHostWithPlugin([aTs, tsconfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([aTs], session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.Completions, @@ -69,7 +66,7 @@ describe("unittests:: tsserver:: with metadataInResponse::", () => { it("can pass through metadata when the command returns object", () => { const host = createHostWithPlugin([aTs, tsconfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([aTs], session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.CompletionInfo, @@ -81,7 +78,7 @@ describe("unittests:: tsserver:: with metadataInResponse::", () => { it("returns undefined correctly", () => { const aTs: File = { path: "/a.ts", content: `class c { prop = "hello"; foo() { const x = 0; } }` }; const host = createHostWithPlugin([aTs, tsconfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([aTs], session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.Completions, diff --git a/src/testRunner/unittests/tsserver/moduleResolution.ts b/src/testRunner/unittests/tsserver/moduleResolution.ts index 92430c080577b..41fdbfae693f2 100644 --- a/src/testRunner/unittests/tsserver/moduleResolution.ts +++ b/src/testRunner/unittests/tsserver/moduleResolution.ts @@ -1,6 +1,3 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { dedent, @@ -22,9 +19,9 @@ import { } from "../helpers/solutionBuilder"; import { baselineTsserverLogs, - createSession, openFilesForSession, protocolTextSpanFromSubstring, + TestSession, verifyGetErrRequest, } from "../helpers/tsserver"; import { @@ -66,7 +63,7 @@ describe("unittests:: tsserver:: moduleResolution", () => { `, }; const host = createServerHost([configFile, fileA, fileB, packageFile, { ...libFile, path: "/a/lib/lib.es2016.full.d.ts" }]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([fileA], session); return { host, @@ -168,7 +165,7 @@ describe("unittests:: tsserver:: moduleResolution", () => { it("node10Result", () => { const host = createServerHost(getFsContentsForNode10Result()); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession(["/home/src/projects/project/index.mts"], session); verifyGetErrRequest({ files: ["/home/src/projects/project/index.mts"], @@ -239,7 +236,7 @@ describe("unittests:: tsserver:: moduleResolution", () => { solutionBuildWithBaseline(host, ["packages/package-b"]); host.clearOutput(); } - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host), canUseEvents: true }); + const session = new TestSession(host); openFilesForSession(["/home/src/projects/project/packages/package-b/src/index.ts"], session); verifyGetErrRequest({ session, diff --git a/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts b/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts index 3022b193bb006..82d885e748a2c 100644 --- a/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts +++ b/src/testRunner/unittests/tsserver/moduleSpecifierCache.ts @@ -1,14 +1,11 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { jsonToReadableText, } from "../helpers"; import { baselineTsserverLogs, - createSession, openFilesForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -144,10 +141,9 @@ describe("unittests:: tsserver:: moduleSpecifierCache", () => { function setup() { const host = createServerHost([aTs, bTs, cTs, bSymlink, ambientDeclaration, tsconfig, packageJson, mobxPackageJson, mobxDts]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([aTs, bTs, cTs], session); - const projectService = session.getProjectService(); - const project = projectService.configuredProjects.get(tsconfig.path)!; + const project = session.getProjectService().configuredProjects.get(tsconfig.path)!; session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.Configure, arguments: { @@ -161,7 +157,7 @@ function setup() { }); triggerCompletions({ file: bTs.path, line: 1, offset: 3 }); - return { host, project, projectService, session, moduleSpecifierCache: project.getModuleSpecifierCache(), triggerCompletions }; + return { host, project, session, moduleSpecifierCache: project.getModuleSpecifierCache(), triggerCompletions }; function triggerCompletions(requestLocation: ts.server.protocol.FileLocationRequestArgs) { session.executeCommandSeq({ diff --git a/src/testRunner/unittests/tsserver/navTo.ts b/src/testRunner/unittests/tsserver/navTo.ts index 4e6563cc4a0a6..8651d8e891de0 100644 --- a/src/testRunner/unittests/tsserver/navTo.ts +++ b/src/testRunner/unittests/tsserver/navTo.ts @@ -1,14 +1,11 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { jsonToReadableText, } from "../helpers"; import { baselineTsserverLogs, - createSession, openFilesForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -27,7 +24,7 @@ describe("unittests:: tsserver:: navigate-to for javascript project", () => { content: "{}", }; const host = createServerHost([file1, configFile, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1], session); // Try to find some interface type defined in lib.d.ts @@ -73,7 +70,7 @@ describe("unittests:: tsserver:: navigate-to for javascript project", () => { export const ghijkl = a.abcdef;`, }; const host = createServerHost([configFile1, file1, configFile2, file2]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1, file2], session); session.executeCommandSeq({ @@ -121,7 +118,7 @@ export const ghijkl = a.abcdef;`, export const ghijkl = a.abcdef;`, }; const host = createServerHost([configFile1, file1, configFile2, file2, solutionConfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1], session); session.executeCommandSeq({ @@ -141,7 +138,7 @@ export const ghijkl = a.abcdef;`, content: "{}", }; const host = createServerHost([file1, configFile, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1], session); // Try to find some interface type defined in lib.d.ts diff --git a/src/testRunner/unittests/tsserver/occurences.ts b/src/testRunner/unittests/tsserver/occurences.ts index 246b5daedf373..c65d9c8238456 100644 --- a/src/testRunner/unittests/tsserver/occurences.ts +++ b/src/testRunner/unittests/tsserver/occurences.ts @@ -1,11 +1,8 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { baselineTsserverLogs, - createSession, openFilesForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -20,7 +17,7 @@ describe("unittests:: tsserver:: occurrence highlight on string", () => { }; const host = createServerHost([file1]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1], session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.DocumentHighlights, diff --git a/src/testRunner/unittests/tsserver/openFile.ts b/src/testRunner/unittests/tsserver/openFile.ts index abdcb943e8f8a..bfc32d87f0859 100644 --- a/src/testRunner/unittests/tsserver/openFile.ts +++ b/src/testRunner/unittests/tsserver/openFile.ts @@ -1,12 +1,8 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { baselineTsserverLogs, closeFilesForSession, - createProjectService, - createSession, + openExternalProjectForSession, openFilesForSession, protocolTextSpanFromSubstring, TestSession, @@ -27,21 +23,17 @@ describe("unittests:: tsserver:: Open-file", () => { }; const projectFileName = "externalProject"; const host = createServerHost([f]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); // create a project - projectService.openExternalProject({ projectFileName, rootFiles: [toExternalFile(f.path)], options: {} }); - - const p = projectService.externalProjects[0]; - // force to load the content of the file - p.updateGraph(); - - const scriptInfo = p.getScriptInfo(f.path)!; - projectService.logger.log(`Snapshot size: ${scriptInfo.getSnapshot().getLength()}`); + openExternalProjectForSession({ + projectFileName, + rootFiles: [toExternalFile(f.path)], + options: {}, + }, session); // open project and replace its content with empty string - projectService.openClientFile(f.path, ""); - projectService.logger.log(`Snapshot size: ${scriptInfo.getSnapshot().getLength()}`); - baselineTsserverLogs("openfile", "realoaded with empty content", projectService); + openFilesForSession([{ file: f, content: "" }], session); + baselineTsserverLogs("openfile", "realoaded with empty content", session); }); function verifyOpenFileWorks(subScenario: string, useCaseSensitiveFileNames: boolean) { @@ -65,7 +57,7 @@ describe("unittests:: tsserver:: Open-file", () => { const host = createServerHost([file1, file2, configFile, configFile2], { useCaseSensitiveFileNames, }); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); // Open file1 -> configFile verifyConfigFileName(file1, "/a"); @@ -77,11 +69,10 @@ describe("unittests:: tsserver:: Open-file", () => { verifyConfigFileName(file2, "/a/b"); verifyConfigFileName(file2, "/a/B"); - baselineTsserverLogs("openfile", subScenario, service); - function verifyConfigFileName(file: File, projectRoot: string) { - const { configFileName } = service.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRoot); - service.logger.log(`file: ${file.path} configFile: ${configFileName}`); - service.closeClientFile(file.path); + baselineTsserverLogs("openfile", subScenario, session); + function verifyConfigFileName(file: File, projectRootPath: string) { + openFilesForSession([{ file, projectRootPath }], session); + closeFilesForSession([file], session); } }); } @@ -89,50 +80,45 @@ describe("unittests:: tsserver:: Open-file", () => { verifyOpenFileWorks("project root is used with case-insensitive system", /*useCaseSensitiveFileNames*/ false); it("uses existing project even if project refresh is pending", () => { - const projectFolder = "/user/someuser/projects/myproject"; + const projectRootPath = "/user/someuser/projects/myproject"; const aFile: File = { - path: `${projectFolder}/src/a.ts`, + path: `${projectRootPath}/src/a.ts`, content: "export const x = 0;", }; const configFile: File = { - path: `${projectFolder}/tsconfig.json`, + path: `${projectRootPath}/tsconfig.json`, content: "{}", }; const files = [aFile, configFile, libFile]; const host = createServerHost(files); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - service.openClientFile(aFile.path, /*fileContent*/ undefined, ts.ScriptKind.TS, projectFolder); + const session = new TestSession(host); + openFilesForSession([{ file: aFile, projectRootPath }], session); const bFile: File = { - path: `${projectFolder}/src/b.ts`, + path: `${projectRootPath}/src/b.ts`, content: `export {}; declare module "./a" { export const y: number; }`, }; host.writeFile(bFile.path, bFile.content); - service.openClientFile(bFile.path, /*fileContent*/ undefined, ts.ScriptKind.TS, projectFolder); - baselineTsserverLogs("openfile", "uses existing project even if project refresh is pending", service); + openFilesForSession([{ file: bFile, projectRootPath }], session); + baselineTsserverLogs("openfile", "uses existing project even if project refresh is pending", session); }); it("can open same file again", () => { - const projectFolder = "/user/someuser/projects/myproject"; + const projectRootPath = "/user/someuser/projects/myproject"; const aFile: File = { - path: `${projectFolder}/src/a.ts`, + path: `${projectRootPath}/src/a.ts`, content: "export const x = 0;", }; const configFile: File = { - path: `${projectFolder}/tsconfig.json`, + path: `${projectRootPath}/tsconfig.json`, content: "{}", }; const files = [aFile, configFile, libFile]; const host = createServerHost(files); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - verifyProject(aFile.content); - verifyProject(`${aFile.content}export const y = 10;`); - baselineTsserverLogs("openfile", "can open same file again", service); - - function verifyProject(aFileContent: string) { - service.openClientFile(aFile.path, aFileContent, ts.ScriptKind.TS, projectFolder); - service.logger.log(`aFileContent: ${service.configuredProjects.get(configFile.path)!.getCurrentProgram()?.getSourceFile(aFile.path)!.text}`); - } + const session = new TestSession(host); + openFilesForSession([{ file: aFile, content: aFile.content, projectRootPath }], session); + openFilesForSession([{ file: aFile, content: `${aFile.content}export const y = 10;`, projectRootPath }], session); + baselineTsserverLogs("openfile", "can open same file again", session); }); it("when file makes edits to add/remove comment directives, they are handled correcrly", () => { @@ -153,7 +139,7 @@ foo(); bar();`, }; const host = createServerHost([file, libFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file], session); verifyGetErrRequest({ session, files: [file] }); @@ -198,7 +184,7 @@ bar();`, "/project/tsconfig.json": "{}", [libFile.path]: libFile.content, }); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); return { host, session }; } diff --git a/src/testRunner/unittests/tsserver/packageJsonInfo.ts b/src/testRunner/unittests/tsserver/packageJsonInfo.ts index a46e10ef9c592..f47b923d882f1 100644 --- a/src/testRunner/unittests/tsserver/packageJsonInfo.ts +++ b/src/testRunner/unittests/tsserver/packageJsonInfo.ts @@ -1,14 +1,11 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { jsonToReadableText, } from "../helpers"; import { baselineTsserverLogs, - createSession, openFilesForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -46,7 +43,7 @@ describe("unittests:: tsserver:: packageJsonInfo::", () => { // Add package.json host.writeFile(packageJson.path, packageJson.content); - session.testhost.baselineHost("Add package.json"); + session.host.baselineHost("Add package.json"); let packageJsonInfo = projectService.packageJsonCache.getInDirectory("/" as ts.Path)!; assert.ok(packageJsonInfo); assert.ok(packageJsonInfo.dependencies); @@ -62,7 +59,7 @@ describe("unittests:: tsserver:: packageJsonInfo::", () => { dependencies: undefined, }), ); - session.testhost.baselineHost("Edit package.json"); + session.host.baselineHost("Edit package.json"); packageJsonInfo = projectService.packageJsonCache.getInDirectory("/" as ts.Path)!; assert.isUndefined(packageJsonInfo.dependencies); @@ -77,7 +74,7 @@ describe("unittests:: tsserver:: packageJsonInfo::", () => { // Delete package.json host.deleteFile(packageJson.path); - session.testhost.baselineHost("delete packageJson"); + session.host.baselineHost("delete packageJson"); assert.isUndefined(projectService.packageJsonCache.getInDirectory("/" as ts.Path)); baselineTsserverLogs("packageJsonInfo", "finds package.json on demand, watches for deletion, and removes them from cache", session); }); @@ -87,7 +84,7 @@ describe("unittests:: tsserver:: packageJsonInfo::", () => { const { session, projectService, host } = setup(); // Add package.json in /src host.writeFile("/src/package.json", packageJson.content); - session.testhost.baselineHost("packageJson"); + session.host.baselineHost("packageJson"); assert.lengthOf(projectService.getPackageJsonsVisibleToFile("/a.ts" as ts.Path), 1); assert.lengthOf(projectService.getPackageJsonsVisibleToFile("/src/b.ts" as ts.Path), 2); baselineTsserverLogs("packageJsonInfo", "finds multiple package.json files when present", session); @@ -101,7 +98,7 @@ describe("unittests:: tsserver:: packageJsonInfo::", () => { assert.isFalse(packageJsonInfo.parseable); host.writeFile(packageJson.path, packageJson.content); - session.testhost.baselineHost("packageJson"); + session.host.baselineHost("packageJson"); projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as ts.Path); const packageJsonInfo2 = projectService.packageJsonCache.getInDirectory("/" as ts.Path)!; assert.ok(packageJsonInfo2); @@ -120,7 +117,7 @@ describe("unittests:: tsserver:: packageJsonInfo::", () => { assert.isFalse(packageJsonInfo.parseable); host.writeFile(packageJson.path, packageJson.content); - session.testhost.baselineHost("PackageJson"); + session.host.baselineHost("PackageJson"); projectService.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as ts.Path); const packageJsonInfo2 = projectService.packageJsonCache.getInDirectory("/" as ts.Path)!; assert.ok(packageJsonInfo2); @@ -134,7 +131,7 @@ describe("unittests:: tsserver:: packageJsonInfo::", () => { function setup(files: readonly File[] = [tsConfig, packageJson]) { const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([files[0]], session); return { host, session, projectService: session.getProjectService() }; } diff --git a/src/testRunner/unittests/tsserver/partialSemanticServer.ts b/src/testRunner/unittests/tsserver/partialSemanticServer.ts index d87f3d790ff1d..af475acc63309 100644 --- a/src/testRunner/unittests/tsserver/partialSemanticServer.ts +++ b/src/testRunner/unittests/tsserver/partialSemanticServer.ts @@ -1,13 +1,10 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { baselineTsserverLogs, closeFilesForSession, - createSession, openFilesForSession, protocolFileLocationFromSubstring, + TestSession, verifyGetErrRequest, } from "../helpers/tsserver"; import { @@ -43,10 +40,10 @@ import { something } from "something"; content: "{}", }; const host = createServerHost([file1, file2, file3, something, libFile, configFile]); - const session = createSession(host, { + const session = new TestSession({ + host, serverMode: ts.LanguageServiceMode.PartialSemantic, useSingleInferredProject: true, - logger: createLoggerWithInMemoryLogs(host), }); return { host, session, file1, file2, file3, something, configFile }; } @@ -105,10 +102,10 @@ import { something } from "something"; const expectedErrorMessage = "')' expected."; const host = createServerHost([file1, libFile, configFile]); - const session = createSession(host, { + const session = new TestSession({ + host, serverMode: ts.LanguageServiceMode.PartialSemantic, useSingleInferredProject: true, - logger: createLoggerWithInMemoryLogs(host), }); const service = session.getProjectService(); @@ -170,10 +167,10 @@ function fooB() { }`, content: "{}", }; const host = createServerHost([file1, file2, file3, something, libFile, configFile]); - const session = createSession(host, { + const session = new TestSession({ + host, serverMode: ts.LanguageServiceMode.PartialSemantic, useSingleInferredProject: true, - logger: createLoggerWithInMemoryLogs(host), }); openFilesForSession([file1], session); baselineTsserverLogs("partialSemanticServer", "should not include referenced files from unopened files", session); @@ -214,7 +211,7 @@ function fooB() { }`, content: "", }; const host = createServerHost([angularFormsDts, angularFormsPackageJson, tsconfig, packageJson, indexTs, libFile]); - const session = createSession(host, { serverMode: ts.LanguageServiceMode.PartialSemantic, useSingleInferredProject: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession({ host, serverMode: ts.LanguageServiceMode.PartialSemantic, useSingleInferredProject: true }); const service = session.getProjectService(); openFilesForSession([indexTs], session); const project = service.inferredProjects[0]; diff --git a/src/testRunner/unittests/tsserver/plugins.ts b/src/testRunner/unittests/tsserver/plugins.ts index fce061c612dcb..073c5b53b2344 100644 --- a/src/testRunner/unittests/tsserver/plugins.ts +++ b/src/testRunner/unittests/tsserver/plugins.ts @@ -1,6 +1,3 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as Harness from "../../_namespaces/Harness"; import * as ts from "../../_namespaces/ts"; import { @@ -8,9 +5,8 @@ import { } from "../helpers"; import { baselineTsserverLogs, - createSession, openFilesForSession, - TestSessionOptions, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -23,7 +19,7 @@ describe("unittests:: tsserver:: plugins:: loading", () => { const testProtocolCommandRequest = "testProtocolCommandRequest"; const testProtocolCommandResponse = "testProtocolCommandResponse"; - function createHostWithPlugin(files: readonly File[], opts?: Partial) { + function createHostWithPlugin(files: readonly File[], globalPlugins?: readonly string[]) { const host = createServerHost(files); host.require = (_initialPath, moduleName) => { session.logger.log(`Loading plugin: ${moduleName}`); @@ -42,7 +38,7 @@ describe("unittests:: tsserver:: plugins:: loading", () => { error: undefined, }; }; - const session = createSession(host, { ...opts, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession({ host, globalPlugins }); return { host, session }; } @@ -74,7 +70,7 @@ describe("unittests:: tsserver:: plugins:: loading", () => { path: "/tsconfig.json", content: "{}", }; - const { session } = createHostWithPlugin([aTs, tsconfig, libFile], { globalPlugins: [...expectedToLoad, ...notToLoad] }); + const { session } = createHostWithPlugin([aTs, tsconfig, libFile], [...expectedToLoad, ...notToLoad]); openFilesForSession([aTs], session); baselineTsserverLogs("plugins", "With global plugins", session); }); @@ -135,7 +131,7 @@ describe("unittests:: tsserver:: plugins:: loading", () => { error: undefined, }; }; - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([aTs], session); session.logger.log(`ExternalFiles:: ${jsonToReadableText(session.getProjectService().configuredProjects.get(tsconfig.path)!.getExternalFiles())}`); @@ -197,7 +193,7 @@ describe("unittests:: tsserver:: plugins:: overriding getSupportedCodeFixes", () error: undefined, }; }; - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([aTs, bTs, cTs], session); // Without arguments session.executeCommandSeq({ @@ -293,7 +289,7 @@ describe("unittests:: tsserver:: plugins:: supportedExtensions::", () => { error: undefined, }; }; - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host), globalPlugins: ["myplugin"] }); + const session = new TestSession({ host, globalPlugins: ["myplugin"] }); openFilesForSession([aTs], session); host.writeFile("/user/username/projects/myproject/c.vue", "cVue file"); diff --git a/src/testRunner/unittests/tsserver/pluginsAsync.ts b/src/testRunner/unittests/tsserver/pluginsAsync.ts index 2772e5d0f689d..cc5a7e4f7ad75 100644 --- a/src/testRunner/unittests/tsserver/pluginsAsync.ts +++ b/src/testRunner/unittests/tsserver/pluginsAsync.ts @@ -1,6 +1,3 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { defer, @@ -8,8 +5,8 @@ import { import { baselineTsserverLogs, closeFilesForSession, - createSession, openFilesForSession, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -19,7 +16,7 @@ import { describe("unittests:: tsserver:: pluginsAsync:: async loaded plugins", () => { function setup(globalPlugins: string[]) { const host = createServerHost([libFile]); - const session = createSession(host, { canUseEvents: true, globalPlugins, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession({ host, globalPlugins }); return { host, session }; } diff --git a/src/testRunner/unittests/tsserver/projectErrors.ts b/src/testRunner/unittests/tsserver/projectErrors.ts index 0fea7f1d5a6ad..f735172ad09a3 100644 --- a/src/testRunner/unittests/tsserver/projectErrors.ts +++ b/src/testRunner/unittests/tsserver/projectErrors.ts @@ -1,6 +1,3 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { jsonToReadableText, @@ -9,11 +6,10 @@ import { appendAllScriptInfos, baselineTsserverLogs, closeFilesForSession, - createProjectService, - createSession, openExternalProjectForSession, openFilesForSession, setCompilerOptionsForInferredProjectsRequestForSession, + TestSession, TestSessionRequest, toExternalFiles, verifyGetErrRequest, @@ -37,7 +33,7 @@ describe("unittests:: tsserver:: projectErrors::", () => { content: "", }; const host = createServerHost([file1, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); const projectFileName = "/a/b/test.csproj"; const compilerOptionsRequest: TestSessionRequest = { command: ts.server.protocol.CommandTypes.CompilerOptionsDiagnosticsFull, @@ -82,7 +78,7 @@ describe("unittests:: tsserver:: projectErrors::", () => { content: jsonToReadableText({ files: [file1, file2].map(f => ts.getBaseFileName(f.path)) }), }; const host = createServerHost([file1, config, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1], session); const compilerOptionsRequest: TestSessionRequest = { command: ts.server.protocol.CommandTypes.CompilerOptionsDiagnosticsFull, @@ -114,7 +110,7 @@ describe("unittests:: tsserver:: projectErrors::", () => { content: correctConfig.content.substr(1), }; const host = createServerHost([file1, file2, corruptedConfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1], session); { @@ -166,7 +162,7 @@ describe("unittests:: tsserver:: projectErrors::", () => { content: correctConfig.content.substr(1), }; const host = createServerHost([file1, file2, correctConfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1], session); { @@ -212,9 +208,9 @@ describe("unittests:: tsserver:: projectErrors:: are reported as appropriate", ( content: "{", }; const host = createServerHost([file1, corruptedConfig]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(file1.path); - baselineTsserverLogs("projectErrors", "document is not contained in project", projectService); + const session = new TestSession(host); + openFilesForSession([file1], session); + baselineTsserverLogs("projectErrors", "document is not contained in project", session); }); describe("when opening new file that doesnt exist on disk yet", () => { @@ -229,7 +225,7 @@ describe("unittests:: tsserver:: projectErrors:: are reported as appropriate", ( content: "class c { }", }; const host = createServerHost([libFile, fileInRoot, fileInProjectRoot]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host), useInferredProjectPerProjectRoot: true }); + const session = new TestSession({ host, useInferredProjectPerProjectRoot: true }); const untitledFile = "untitled:Untitled-1"; const refPathNotFound1 = "../../../../../../typings/@epic/Core.d.ts"; @@ -276,7 +272,7 @@ describe("unittests:: tsserver:: projectErrors:: are reported as appropriate", ( content: jsonToReadableText({ compilerOptions: { module: "none", targer: "es5" }, exclude: ["node_modules"] }), }; const host = createServerHost([app, foo, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.Open, @@ -297,7 +293,7 @@ describe("unittests:: tsserver:: projectErrors:: are reported as appropriate", ( content: "let x: number = false;", }; const host = createServerHost([file, libFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.Geterr, arguments: { @@ -325,7 +321,7 @@ describe("unittests:: tsserver:: projectErrors:: are reported as appropriate", ( }; const files = [libFile, app, serverUtilities, backendTest]; const host = createServerHost(files); - const session = createSession(host, { useInferredProjectPerProjectRoot: true, canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession({ host, useInferredProjectPerProjectRoot: true }); openFilesForSession([{ file: app, projectRootPath: "/user/username/projects/myproject" }], session); openFilesForSession([{ file: backendTest, projectRootPath: "/user/username/projects/myproject" }], session); verifyGetErrRequest({ session, files: [backendTest.path, app.path] }); @@ -363,7 +359,7 @@ declare module '@custom/plugin' { }; const files = [libFile, aFile, config, plugin, pluginProposed]; const host = createServerHost(files); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([aFile], session); verifyGetErrRequest({ session, files: [aFile] }); @@ -421,7 +417,7 @@ describe("unittests:: tsserver:: Project Errors for Configure file diagnostics e }`, }; const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file], session); baselineTsserverLogs("projectErrors", "configFileDiagnostic events are generated when the config file has errors", session); }); @@ -438,7 +434,7 @@ describe("unittests:: tsserver:: Project Errors for Configure file diagnostics e }`, }; const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file], session); baselineTsserverLogs("projectErrors", "configFileDiagnostic events are generated when the config file doesnt have errors", session); }); @@ -456,7 +452,7 @@ describe("unittests:: tsserver:: Project Errors for Configure file diagnostics e }; const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file], session); configFile.content = `{ @@ -499,7 +495,7 @@ describe("unittests:: tsserver:: Project Errors for Configure file diagnostics e }`, }; const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file2], session); openFilesForSession([file], session); // We generate only if project is created when opening file from the project @@ -522,7 +518,7 @@ describe("unittests:: tsserver:: Project Errors for Configure file diagnostics e }`, }; const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, suppressDiagnosticEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession({ host, suppressDiagnosticEvents: true }); openFilesForSession([file], session); baselineTsserverLogs("projectErrors", "configFileDiagnostic events are not generated when the config file has errors but suppressDiagnosticEvents is true", session); }); @@ -548,7 +544,7 @@ describe("unittests:: tsserver:: Project Errors for Configure file diagnostics e }; const host = createServerHost([file, file2, file3, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file2], session); openFilesForSession([file], session); // We generate only if project is created when opening file from the project @@ -571,7 +567,7 @@ describe("unittests:: tsserver:: Project Errors for Configure file diagnostics e }; const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file], session); baselineTsserverLogs("projectErrors", "configFileDiagnostic events contains the project reference errors", session); }); @@ -584,7 +580,7 @@ describe("unittests:: tsserver:: projectErrors:: dont include overwrite emit err content: "function test1() { }", }; const host = createServerHost([f1, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([f1], session); const projectService = session.getProjectService(); @@ -594,7 +590,7 @@ describe("unittests:: tsserver:: projectErrors:: dont include overwrite emit err arguments: { projectFileName }, }); - setCompilerOptionsForInferredProjectsRequestForSession({ module: ts.ModuleKind.CommonJS }, session); + setCompilerOptionsForInferredProjectsRequestForSession({ module: ts.server.protocol.ModuleKind.CommonJS }, session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.CompilerOptionsDiagnosticsFull, arguments: { projectFileName }, @@ -608,7 +604,7 @@ describe("unittests:: tsserver:: projectErrors:: dont include overwrite emit err content: "function test1() { }", }; const host = createServerHost([f1, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); const projectFileName = "/a/b/project.csproj"; const externalFiles = toExternalFiles([f1.path]); openExternalProjectForSession({ @@ -625,7 +621,7 @@ describe("unittests:: tsserver:: projectErrors:: dont include overwrite emit err openExternalProjectForSession({ projectFileName, rootFiles: externalFiles, - options: { module: ts.ModuleKind.CommonJS }, + options: { module: ts.server.protocol.ModuleKind.CommonJS }, }, session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.CompilerOptionsDiagnosticsFull, @@ -658,7 +654,7 @@ describe("unittests:: tsserver:: projectErrors:: reports Options Diagnostic loca content: configFileContentWithComment, }; const host = createServerHost([file, libFile, configFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file], session); session.executeCommandSeq({ @@ -683,7 +679,7 @@ describe("unittests:: tsserver:: projectErrors:: with config file change", () => const tsconfig: File = { path: "/tsconfig.json", content: options(/*allowUnusedLabels*/ true) }; const host = createServerHost([aTs, tsconfig]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([aTs], session); host.modifyFile(tsconfig.path, options(/*allowUnusedLabels*/ false)); @@ -722,7 +718,7 @@ console.log(blabla);`, }; const host = createServerHost([test, blabla, libFile, tsconfig]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([test], session); return { host, session, test, blabla, tsconfig }; } @@ -761,7 +757,7 @@ describe("unittests:: tsserver:: projectErrors:: with npm install when", () => { }; const projectFiles = [main, libFile, config]; const host = createServerHost(projectFiles); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([{ file: main, projectRootPath: "/user/username/projects/myproject" }], session); verifyGetErrRequest({ session, files: [main] }); @@ -805,9 +801,6 @@ describe("unittests:: tsserver:: projectErrors:: with npm install when", () => { host.runQueuedTimeoutCallbacks(); // Invalidation of failed lookups host.runQueuedTimeoutCallbacks(); // Actual update } - else { - session.testhost.logTimeoutQueueLength(); - } verifyGetErrRequest({ session, files: [main], existingTimeouts: !npmInstallComplete && !timeoutDuringPartialInstallation }); } } diff --git a/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts b/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts index 4ac135e35681f..004816c0a0a75 100644 --- a/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts +++ b/src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts @@ -1,6 +1,3 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { jsonToReadableText, @@ -10,9 +7,9 @@ import { } from "../helpers/solutionBuilder"; import { baselineTsserverLogs, - createSession, openFilesForSession, protocolToLocation, + TestSession, } from "../helpers/tsserver"; import { createServerHost, @@ -61,7 +58,7 @@ fn2(); describe("Of usageTs", () => { it("with initial file open, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); // Verify CompileOnSaveAffectedFileList @@ -85,7 +82,7 @@ fn2(); }); it("with initial file open, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); // Verify CompileOnSaveAffectedFileList @@ -109,7 +106,7 @@ fn2(); }); it("with local change to dependency, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); session.executeCommandSeq({ @@ -139,7 +136,7 @@ fn2(); }); it("with local change to dependency, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); session.executeCommandSeq({ @@ -169,7 +166,7 @@ fn2(); }); it("with local change to usage, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); session.executeCommandSeq({ @@ -210,7 +207,7 @@ fn2(); }); it("with local change to usage, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); session.executeCommandSeq({ @@ -251,7 +248,7 @@ fn2(); }); it("with change to dependency, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); session.executeCommandSeq({ @@ -281,7 +278,7 @@ fn2(); }); it("with change to dependency, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); session.executeCommandSeq({ @@ -311,7 +308,7 @@ fn2(); }); it("with change to usage, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); session.executeCommandSeq({ @@ -352,7 +349,7 @@ fn2(); }); it("with change to usage, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); session.executeCommandSeq({ @@ -396,7 +393,7 @@ fn2(); describe("Of dependencyTs in usage project", () => { it("with initial file open, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); // Verify CompileOnSaveAffectedFileList @@ -420,7 +417,7 @@ fn2(); }); it("with initial file open, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); // Verify CompileOnSaveAffectedFileList @@ -444,7 +441,7 @@ fn2(); }); it("with local change to dependency, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); session.executeCommandSeq({ @@ -474,7 +471,7 @@ fn2(); }); it("with local change to dependency, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); session.executeCommandSeq({ @@ -504,7 +501,7 @@ fn2(); }); it("with local change to usage, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); session.executeCommandSeq({ @@ -545,7 +542,7 @@ fn2(); }); it("with local change to usage, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); session.executeCommandSeq({ @@ -586,7 +583,7 @@ fn2(); }); it("with change to dependency, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); session.executeCommandSeq({ @@ -616,7 +613,7 @@ fn2(); }); it("with change to dependency, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); session.executeCommandSeq({ @@ -646,7 +643,7 @@ fn2(); }); it("with change to usage, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); session.executeCommandSeq({ @@ -687,7 +684,7 @@ fn2(); }); it("with change to usage, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs], session); session.executeCommandSeq({ @@ -733,7 +730,7 @@ fn2(); describe("Of usageTs", () => { it("with initial file open, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); // Verify CompileOnSaveAffectedFileList @@ -757,7 +754,7 @@ fn2(); }); it("with initial file open, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); // Verify CompileOnSaveAffectedFileList @@ -781,7 +778,7 @@ fn2(); }); it("with local change to dependency, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -822,7 +819,7 @@ fn2(); }); it("with local change to dependency, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -863,7 +860,7 @@ fn2(); }); it("with local change to usage, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -904,7 +901,7 @@ fn2(); }); it("with local change to usage, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -945,7 +942,7 @@ fn2(); }); it("with change to dependency, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -986,7 +983,7 @@ fn2(); }); it("with change to dependency, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -1027,7 +1024,7 @@ fn2(); }); it("with change to usage, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -1068,7 +1065,7 @@ fn2(); }); it("with change to usage, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -1111,7 +1108,7 @@ fn2(); describe("Of dependencyTs in usage project", () => { it("with initial file open, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); // Verify CompileOnSaveAffectedFileList @@ -1135,7 +1132,7 @@ fn2(); }); it("with local change to dependency, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -1176,7 +1173,7 @@ fn2(); }); it("with local change to usage, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -1217,7 +1214,7 @@ fn2(); }); it("with change to dependency, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -1258,7 +1255,7 @@ fn2(); }); it("with change to usage, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -1302,7 +1299,7 @@ fn2(); describe("Of dependencyTs", () => { it("with initial file open, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); // Verify CompileOnSaveAffectedFileList @@ -1326,7 +1323,7 @@ fn2(); }); it("with initial file open, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); // Verify CompileOnSaveAffectedFileList @@ -1350,7 +1347,7 @@ fn2(); }); it("with local change to dependency, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -1391,7 +1388,7 @@ fn2(); }); it("with local change to dependency, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -1432,7 +1429,7 @@ fn2(); }); it("with local change to usage, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -1473,7 +1470,7 @@ fn2(); }); it("with local change to usage, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -1514,7 +1511,7 @@ fn2(); }); it("with change to dependency, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -1555,7 +1552,7 @@ fn2(); }); it("with change to dependency, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -1596,7 +1593,7 @@ fn2(); }); it("with change to usage, without specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -1637,7 +1634,7 @@ fn2(); }); it("with change to usage, with specifying project file", () => { const host = createServerHost([dependencyTs, dependencyConfig, usageTs, usageConfig, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([usageTs, dependencyTs], session); session.executeCommandSeq({ @@ -1741,7 +1738,7 @@ describe("unittests:: tsserver:: with project references and compile on save wit // ts build should succeed ensureErrorFreeBuild(host, [siblingConfig.path]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([siblingSource], session); session.executeCommandSeq({ diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index 6f904e0424737..23c0e5b2aa6d0 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -1,6 +1,3 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { dedent, @@ -14,11 +11,10 @@ import { import { baselineTsserverLogs, createHostWithSolutionBuild, - createProjectService, - createSession, openFilesForSession, protocolFileLocationFromSubstring, protocolLocationFromSubstring, + TestSession, verifyGetErrRequest, } from "../helpers/tsserver"; import { @@ -119,7 +115,7 @@ describe("unittests:: tsserver:: with project references and tsbuild", () => { const files = [libFile, containerLibConfig, containerLibIndex, containerExecConfig, containerExecIndex, containerCompositeExecConfig, containerCompositeExecIndex, containerConfig]; if (tempFile) files.push(tempFile); const host = createHostWithSolutionBuild(files, [containerConfig.path]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); return { files, session, containerConfig, containerCompositeExecIndex }; } @@ -260,7 +256,7 @@ function foo() { [commonConfig, keyboardTs, keyboardTestTs, srcConfig, terminalTs, libFile], [srcConfig.path], ); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([keyboardTs, terminalTs], session); const searchStr = "evaluateKeyboardEvent"; @@ -331,23 +327,23 @@ function foo() { }; const files = [libFile, aTs, a2Ts, configA, bDts, bTs, configB, cTs, configC]; const host = createServerHost(files); - const service = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - service.openClientFile(aTs.path); + const session = new TestSession(host); + openFilesForSession([aTs], session); // project A referencing b.d.ts without project reference - const projectA = service.configuredProjects.get(configA.path)!; + const projectA = session.getProjectService().configuredProjects.get(configA.path)!; assert.isDefined(projectA); // reuses b.d.ts but sets the path and resolved path since projectC has project references // as the real resolution was to b.ts - service.openClientFile(cTs.path); + openFilesForSession([cTs], session); // Now new project for project A tries to reuse b but there is no filesByName mapping for b's source location host.writeFile(a2Ts.path, `${a2Ts.content}export const y = 30;`); - service.testhost.baselineHost("a2Ts modified"); + session.host.baselineHost("a2Ts modified"); assert.isTrue(projectA.dirty); projectA.updateGraph(); - baselineTsserverLogs("projectReferences", "reusing d.ts files from composite and non composite projects", service); + baselineTsserverLogs("projectReferences", "reusing d.ts files from composite and non composite projects", session); }); describe("when references are monorepo like with symlinks", () => { @@ -389,7 +385,7 @@ function foo() { createServerHost(files); // Create symlink in node module - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([aTest], session); verifyGetErrRequest({ session, files: [aTest] }); session.executeCommandSeq({ @@ -548,7 +544,7 @@ testCompositeFunction('why hello there', 42);`, symLink: `/user/username/projects/myproject/packages/emit-composite`, }; const host = createServerHost([libFile, compositeConfig, compositePackageJson, compositeIndex, compositeTestModule, consumerConfig, consumerIndex, symlink], { useCaseSensitiveFileNames: true }); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([consumerIndex], session); verifyGetErrRequest({ session, files: [consumerIndex] }); baselineTsserverLogs("projectReferences", `when the referenced projects have allowJs and emitDeclarationOnly`, session); @@ -618,7 +614,7 @@ testCompositeFunction('why hello there', 42);`, const files = [libFile, solution, compilerConfig, typesFile, programFile, servicesConfig, servicesFile, libFile]; const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([programFile], session); // Find all references for getSourceFile @@ -738,7 +734,7 @@ testCompositeFunction('why hello there', 42);`, const files = [libFile, solutionConfig, aConfig, aFile, bConfig, bFile, cConfig, cFile, dConfig, dFile, libFile]; const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([bFile], session); // The first search will trigger project loads @@ -812,7 +808,7 @@ ${usage}`, content: definition, }; const host = createServerHost([libFile, solution, libFile, apiConfig, apiFile, appConfig, appFile, sharedConfig, sharedFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([apiFile], session); // Find all references @@ -929,7 +925,7 @@ export const foo = local;`, const files = [libFile, solution, compilerConfig, typesFile, programFile, servicesConfig, servicesFile, libFile]; const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([programFile], session); // Find all references @@ -1047,7 +1043,7 @@ export function bar() {}`, fileResolvingToMainDts, ...additionalFiles, ]); - const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); const service = session.getProjectService(); service.openClientFile(main.path); return { session, service, host }; @@ -1324,7 +1320,7 @@ bar;`, content: `class class2 {}`, }; const host = createServerHost([config1, class1, class1Dts, config2, class2, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([class2], session); return { host, session, class1 }; } @@ -1479,7 +1475,7 @@ bar;`, solutionBuildWithBaseline(host, [solnConfig.path]); host.clearOutput(); } - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([appIndex], session); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.GetCodeFixes, @@ -1561,7 +1557,7 @@ bar;`, noCoreRef2File, noCoreRef2Config, ], { useCaseSensitiveFileNames: true }); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([mainFile, coreFile], session); // Find all refs in coreFile @@ -1652,7 +1648,7 @@ const b: B = new B();`, }; const host = createServerHost([configA, indexA, configB, indexB, helperB, dtsB, ...(dtsMapPresent ? [dtsMapB] : [])]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([indexA, ...(projectAlreadyLoaded ? [helperB] : [])], session); session.executeCommandSeq({ diff --git a/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts b/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts index cd39268fc47fa..29678cdec1c77 100644 --- a/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts +++ b/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts @@ -1,6 +1,3 @@ -import { - createLoggerWithInMemoryLogs, -} from "../../../harness/tsserverLogger"; import * as ts from "../../_namespaces/ts"; import { jsonToReadableText, @@ -9,7 +6,6 @@ import { baselineTsserverLogs, closeFilesForSession, createHostWithSolutionBuild, - createSession, openFilesForSession, TestSession, TestSessionRequest, @@ -248,14 +244,14 @@ fn5(); }), ); onHostCreate?.(host); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); return { host, session }; } function createSessionWithProjectReferences(onHostCreate?: OnHostCreate) { const host = createHostWithSolutionBuild(files, [mainConfig.path]); onHostCreate?.(host); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); return { host, session }; } @@ -274,7 +270,7 @@ fn5(); }), ); onHostCreate?.(host); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); return { host, session }; } @@ -855,7 +851,7 @@ ${dependencyTs.content}`, it("when projects are not built", () => { const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([mainTs, randomFile], session); verifyAllFnAction( session, @@ -1671,7 +1667,7 @@ ${dependencyTs.content}`, it("when projects are not built", () => { const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([dependencyTs, randomFile], session); verifyAllFnAction( session, @@ -2723,7 +2719,7 @@ ${dependencyTs.content}`, it("when projects are not built", () => { const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([mainTs, dependencyTs, randomFile], session); verifyAllFnAction( session, diff --git a/src/testRunner/unittests/tsserver/projects.ts b/src/testRunner/unittests/tsserver/projects.ts index e65c2c6abdfc9..93c1f54b80a25 100644 --- a/src/testRunner/unittests/tsserver/projects.ts +++ b/src/testRunner/unittests/tsserver/projects.ts @@ -12,13 +12,13 @@ import { import { baselineTsserverLogs, closeFilesForSession, - createProjectService, - createSession, logConfiguredProjectsHasOpenRefStatus, logInferredProjectsOrphanStatus, openExternalProjectForSession, openFilesForSession, protocolFileLocationFromSubstring, + setCompilerOptionsForInferredProjectsRequestForSession, + TestSession, TestSessionRequest, toExternalFile, toExternalFiles, @@ -42,7 +42,7 @@ describe("unittests:: tsserver:: projects::", () => { let x = y`, }; const host = createServerHost([file1, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([file1], session); // Two errors: CommonFile2 not found and cannot find name y @@ -70,7 +70,7 @@ describe("unittests:: tsserver:: projects::", () => { }; const files = [commonFile1, commonFile2, configFile]; const host = createServerHost(files); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); openFilesForSession([commonFile1], session); configFile.content = `{ @@ -105,13 +105,25 @@ describe("unittests:: tsserver:: projects::", () => { const proj1name = "proj1", proj2name = "proj2", proj3name = "proj3"; const host = createServerHost([file1, file2, file3]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); - openExternalProjectForSession({ rootFiles: toExternalFiles([file1.path]), options: {}, projectFileName: proj1name }, session); + openExternalProjectForSession({ + rootFiles: toExternalFiles([file1.path]), + options: {}, + projectFileName: proj1name, + }, session); - openExternalProjectForSession({ rootFiles: toExternalFiles([file2.path]), options: {}, projectFileName: proj2name }, session); + openExternalProjectForSession({ + rootFiles: toExternalFiles([file2.path]), + options: {}, + projectFileName: proj2name, + }, session); - openExternalProjectForSession({ rootFiles: toExternalFiles([file3.path]), options: {}, projectFileName: proj3name }, session); + openExternalProjectForSession({ + rootFiles: toExternalFiles([file3.path]), + options: {}, + projectFileName: proj3name, + }, session); baselineTsserverLogs("projects", "should disable features when the files are too large", session); }); @@ -127,12 +139,16 @@ describe("unittests:: tsserver:: projects::", () => { fileSize: 100, }; - const projName = "proj1"; + const projectFileName = "proj1"; const host = createServerHost([file1, file2]); - const session = createSession(host, { useSingleInferredProject: true, logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession({ host, useSingleInferredProject: true }); - openExternalProjectForSession({ rootFiles: toExternalFiles([file1.path, file2.path]), options: {}, projectFileName: projName }, session); + openExternalProjectForSession({ + rootFiles: toExternalFiles([file1.path, file2.path]), + options: {}, + projectFileName, + }, session); openFilesForSession([file2], session); baselineTsserverLogs("projects", "should not crash when opening a file in a project with a disabled language service", session); @@ -154,16 +170,24 @@ describe("unittests:: tsserver:: projects::", () => { ), }; - const externalProjectName = "externalproject"; + const projectFileName = "externalproject"; const host = createServerHost([file1, config1]); - const projectService = createProjectService(host, { useSingleInferredProject: true, serverMode: ts.LanguageServiceMode.Syntactic, logger: createLoggerWithInMemoryLogs(host) }); - projectService.openExternalProject({ - rootFiles: toExternalFiles([file1.path, config1.path]), - options: {}, - projectFileName: externalProjectName, - }); - - baselineTsserverLogs("projects", "external project including config file", projectService); + const session = new TestSession({ host, useSingleInferredProject: true, serverMode: ts.LanguageServiceMode.Syntactic }); + const request: ts.server.protocol.OpenExternalProjectRequest = { + command: ts.server.protocol.CommandTypes.OpenExternalProject, + arguments: { + rootFiles: toExternalFiles([file1.path, config1.path]), + options: {}, + projectFileName, + }, + seq: session.getNextSeq(), + type: "request", + }; + session.host.baselineHost("Before request"); + session.logger.info(`request:${ts.server.stringifyIndented(request)}`); + session.getProjectService().openExternalProject(request.arguments); + session.host.baselineHost("After request"); + baselineTsserverLogs("projects", "external project including config file", session); }); it("loose file included in config file (openClientFile)", () => { @@ -182,9 +206,9 @@ describe("unittests:: tsserver:: projects::", () => { }; const host = createServerHost([file1, config1]); - const projectService = createProjectService(host, { useSingleInferredProject: true, serverMode: ts.LanguageServiceMode.Syntactic, logger: createLoggerWithInMemoryLogs(host) }); - projectService.openClientFile(file1.path, file1.content); - baselineTsserverLogs("projects", "loose file included in config file (openClientFile)", projectService); + const session = new TestSession({ host, useSingleInferredProject: true, serverMode: ts.LanguageServiceMode.Syntactic }); + openFilesForSession([{ file: file1, content: file1.content }], session); + baselineTsserverLogs("projects", "loose file included in config file (openClientFile)", session); }); it("loose file included in config file (applyCodeChanges)", () => { @@ -203,10 +227,15 @@ describe("unittests:: tsserver:: projects::", () => { }; const host = createServerHost([file1, config1]); - const projectService = createProjectService(host, { useSingleInferredProject: true, serverMode: ts.LanguageServiceMode.Syntactic, logger: createLoggerWithInMemoryLogs(host) }); - projectService.applyChangesInOpenFiles(ts.singleIterator({ fileName: file1.path, content: file1.content })); + const session = new TestSession({ host, useSingleInferredProject: true, serverMode: ts.LanguageServiceMode.Syntactic }); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.ApplyChangedToOpenFiles, + arguments: { + openFiles: [{ fileName: file1.path, content: file1.content }], + }, + }); - baselineTsserverLogs("projects", "loose file included in config file (applyCodeChanges)", projectService); + baselineTsserverLogs("projects", "loose file included in config file (applyCodeChanges)", session); }); }); @@ -221,8 +250,12 @@ describe("unittests:: tsserver:: projects::", () => { }; const host = createServerHost([f1, f2, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openExternalProjectForSession({ projectFileName: "/a/b/project", rootFiles: toExternalFiles([f1.path, f2.path]), options: {} }, session); + const session = new TestSession(host); + openExternalProjectForSession({ + projectFileName: "/a/b/project", + rootFiles: toExternalFiles([f1.path, f2.path]), + options: {}, + }, session); openFilesForSession([f1, { file: f2.path, content: "let x: string" }], session); // should contain completions for string @@ -251,8 +284,12 @@ describe("unittests:: tsserver:: projects::", () => { }; const host = createServerHost([f1, f2, libFile]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); - openExternalProjectForSession({ projectFileName: "/a/b/project", rootFiles: [{ fileName: f1.path }, { fileName: f2.path, hasMixedContent: true }], options: {} }, session); + const session = new TestSession(host); + openExternalProjectForSession({ + projectFileName: "/a/b/project", + rootFiles: [{ fileName: f1.path }, { fileName: f2.path, hasMixedContent: true }], + options: {}, + }, session); openFilesForSession([f1, { file: f2.path, content: "let somelongname: string" }], session); session.executeCommandSeq({ @@ -282,16 +319,14 @@ describe("unittests:: tsserver:: projects::", () => { content: `export let y = 1;`, }; const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - - projectService.openClientFile(file1.path); + const session = new TestSession(host); - projectService.openClientFile(file3.path); + openFilesForSession([file1, file3], session); host.writeFile(file2.path, `export * from "../c/f3"`); // now inferred project should inclule file3 host.runQueuedTimeoutCallbacks(); - logInferredProjectsOrphanStatus(projectService); - baselineTsserverLogs("projects", "changes in closed files are reflected in project structure", projectService); + logInferredProjectsOrphanStatus(session); + baselineTsserverLogs("projects", "changes in closed files are reflected in project structure", session); }); it("deleted files affect project structure", () => { @@ -308,14 +343,13 @@ describe("unittests:: tsserver:: projects::", () => { content: `export let y = 1;`, }; const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); - projectService.openClientFile(file1.path); - projectService.openClientFile(file3.path); + openFilesForSession([file1, file3], session); host.deleteFile(file2.path); host.runQueuedTimeoutCallbacks(); - baselineTsserverLogs("projects", "deleted files affect project structure", projectService); + baselineTsserverLogs("projects", "deleted files affect project structure", session); }); it("ignores files excluded by a custom safe type list", () => { @@ -328,15 +362,19 @@ describe("unittests:: tsserver:: projects::", () => { content: "whoa do @@ not parse me ok thanks!!!", }; const host = createServerHost([file1, office, customTypesMap]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); try { - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, office.path]) }); - projectService.logger.log(`TypeAcquisition:: ${jsonToReadableText(projectService.externalProjects[0].getTypeAcquisition())}`); + openExternalProjectForSession({ + projectFileName: "project", + options: {}, + rootFiles: toExternalFiles([file1.path, office.path]), + }, session); + session.logger.log(`TypeAcquisition:: ${jsonToReadableText(session.getProjectService().externalProjects[0].getTypeAcquisition())}`); } finally { - projectService.resetSafeList(); + session.getProjectService().resetSafeList(); } - baselineTsserverLogs("projects", "ignores files excluded by a custom safe type list", projectService); + baselineTsserverLogs("projects", "ignores files excluded by a custom safe type list", session); }); it("file with name constructor.js doesnt cause issue with typeAcquisition when safe type list", () => { @@ -353,50 +391,15 @@ describe("unittests:: tsserver:: projects::", () => { content: "export function is() { return true; }", }; const host = createServerHost([file1, libFile, constructorFile, bliss, customTypesMap]); - let request: string | undefined; - const cachePath = "/a/data"; - const typingsInstaller: ts.server.ITypingsInstaller = { - isKnownTypesPackageName: ts.returnFalse, - installPackage: ts.notImplemented, - enqueueInstallTypingsRequest: (proj, typeAcquisition, unresolvedImports) => { - assert.isUndefined(request); - request = jsonToReadableText(ts.server.createInstallTypingsRequest(proj, typeAcquisition, unresolvedImports || ts.server.emptyArray, cachePath)); - }, - attach: ts.noop, - onProjectClosed: ts.noop, - globalTypingsCacheLocation: cachePath, - }; - - const projectName = "project"; - const projectService = createProjectService(host, { typingsInstaller, logger: createLoggerWithInMemoryLogs(host) }); - projectService.openExternalProject({ projectFileName: projectName, options: {}, rootFiles: toExternalFiles([file1.path, constructorFile.path, bliss.path]) }); - assert.equal( - request, - jsonToReadableText({ - projectName, - fileNames: [libFile.path, file1.path, constructorFile.path, bliss.path], - compilerOptions: { allowNonTsExtensions: true, noEmitForJsFiles: true }, - typeAcquisition: { include: ["blissfuljs"], exclude: [], enable: true }, - unresolvedImports: ["s"], - projectRootPath: "/", - cachePath, - kind: "discover", - }), - ); - const response = JSON.parse(request!); - request = undefined; - projectService.updateTypingsForProject({ - kind: "action::set", - projectName: response.projectName, - typeAcquisition: response.typeAcquisition, - compilerOptions: response.compilerOptions, - typings: ts.emptyArray, - unresolvedImports: response.unresolvedImports, - }); + const projectFileName = "project"; + const session = new TestSession(host); + openExternalProjectForSession({ + projectFileName, + options: {}, + rootFiles: toExternalFiles([file1.path, constructorFile.path, bliss.path]), + }, session); - projectService.testhost.logTimeoutQueueLength(); - assert.isUndefined(request); - baselineTsserverLogs("projects", "file with name constructor.js doesnt cause issue with typeAcquisition when safe type list", projectService); + baselineTsserverLogs("projects", "file with name constructor.js doesnt cause issue with typeAcquisition when safe type list", session); }); it("ignores files excluded by the default type list", () => { @@ -430,15 +433,19 @@ describe("unittests:: tsserver:: projects::", () => { }; const files = [file1, minFile, kendoFile1, kendoFile2, kendoFile3, officeFile1, officeFile2]; const host = createServerHost(files); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); try { - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles(files.map(f => f.path)) }); - projectService.logger.log(`TypeAcquisition:: ${jsonToReadableText(projectService.externalProjects[0].getTypeAcquisition())}`); + openExternalProjectForSession({ + projectFileName: "project", + options: {}, + rootFiles: toExternalFiles(files.map(f => f.path)), + }, session); + session.logger.log(`TypeAcquisition:: ${jsonToReadableText(session.getProjectService().externalProjects[0].getTypeAcquisition())}`); } finally { - projectService.resetSafeList(); + session.getProjectService().resetSafeList(); } - baselineTsserverLogs("projects", "ignores files excluded by the default type list", projectService); + baselineTsserverLogs("projects", "ignores files excluded by the default type list", session); }); it("removes version numbers correctly", () => { @@ -471,14 +478,19 @@ describe("unittests:: tsserver:: projects::", () => { content: "let y = 5", }; const host = createServerHost([file1, file2, file3, customTypesMap]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); try { - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]), typeAcquisition: { enable: true } }); + openExternalProjectForSession({ + projectFileName: "project", + options: {}, + rootFiles: toExternalFiles([file1.path, file2.path]), + typeAcquisition: { enable: true }, + }, session); } finally { - projectService.resetSafeList(); + session.getProjectService().resetSafeList(); } - baselineTsserverLogs("projects", "ignores files excluded by a legacy safe type list", projectService); + baselineTsserverLogs("projects", "ignores files excluded by a legacy safe type list", session); }); it("correctly migrate files between projects", () => { @@ -497,49 +509,48 @@ describe("unittests:: tsserver:: projects::", () => { content: "export let y = 1;", }; const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); - projectService.openClientFile(file2.path); - logInferredProjectsOrphanStatus(projectService); + openFilesForSession([file2], session); + logInferredProjectsOrphanStatus(session); - projectService.openClientFile(file3.path); - logInferredProjectsOrphanStatus(projectService); + openFilesForSession([file3], session); + logInferredProjectsOrphanStatus(session); - projectService.openClientFile(file1.path); - logInferredProjectsOrphanStatus(projectService); + openFilesForSession([file1], session); + logInferredProjectsOrphanStatus(session); - projectService.closeClientFile(file1.path); - logInferredProjectsOrphanStatus(projectService); + closeFilesForSession([file1], session); + logInferredProjectsOrphanStatus(session); - projectService.closeClientFile(file3.path); - logInferredProjectsOrphanStatus(projectService); + closeFilesForSession([file3], session); + logInferredProjectsOrphanStatus(session); - projectService.openClientFile(file3.path); - logInferredProjectsOrphanStatus(projectService); - baselineTsserverLogs("projects", "correctly migrate files between projects", projectService); + openFilesForSession([file3], session); + logInferredProjectsOrphanStatus(session); + baselineTsserverLogs("projects", "correctly migrate files between projects", session); }); it("regression test for crash in acquireOrUpdateDocument", () => { - const tsFile = { - fileName: "/a/b/file1.ts", - path: "/a/b/file1.ts", - content: "", - }; - const jsFile = { - path: "/a/b/file1.js", - content: "var x = 10;", - fileName: "/a/b/file1.js", - scriptKind: "JS" as const, - }; - const host = createServerHost([]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.applyChangesInOpenFiles(ts.singleIterator(tsFile)); - const projs = projectService.synchronizeProjectList([]); - projectService.findProject(projs[0].info!.projectName)!.getLanguageService().getNavigationBarItems(tsFile.fileName); - projectService.synchronizeProjectList([projs[0].info!]); - projectService.applyChangesInOpenFiles(ts.singleIterator(jsFile)); - baselineTsserverLogs("projects", "regression test for crash in acquireOrUpdateDocument", projectService); + const session = new TestSession(host); + openFilesForSession(["/a/b/file1.ts"], session); + const projs = session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.SynchronizeProjectList, + arguments: { knownProjects: [] }, + }).response as ts.server.protocol.ProjectFilesWithDiagnostics[]; + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.NavBar, + arguments: { file: "/a/b/file1.ts" }, + }); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.SynchronizeProjectList, + arguments: { + knownProjects: projs.map(p => p.info!), + }, + }); + openFilesForSession([{ file: "/a/b/file1.js", content: "var x = 10;" }], session); + baselineTsserverLogs("projects", "regression test for crash in acquireOrUpdateDocument", session); }); it("config file is deleted", () => { @@ -556,15 +567,13 @@ describe("unittests:: tsserver:: projects::", () => { content: jsonToReadableText({ compilerOptions: {} }), }; const host = createServerHost([file1, file2, config]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - - projectService.openClientFile(file1.path); + const session = new TestSession(host); - projectService.openClientFile(file2.path); + openFilesForSession([file1, file2], session); host.deleteFile(config.path); host.runQueuedTimeoutCallbacks(); - baselineTsserverLogs("projects", "config file is deleted", projectService); + baselineTsserverLogs("projects", "config file is deleted", session); }); it("loading files with correct priority", () => { @@ -587,23 +596,26 @@ describe("unittests:: tsserver:: projects::", () => { }), }; const host = createServerHost([f1, f2, f3, config]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); - projectService.setHostConfiguration({ - extraFileExtensions: [ - { extension: ".js", isMixedContent: false }, - { extension: ".html", isMixedContent: true }, - ], + const session = new TestSession(host); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.Configure, + arguments: { + extraFileExtensions: [ + { extension: ".js", isMixedContent: false }, + { extension: ".html", isMixedContent: true }, + ], + }, }); - projectService.openClientFile(f1.path); + openFilesForSession([f1], session); // Since f2 refers to config file as the default project, it needs to be kept alive - projectService.closeClientFile(f1.path); - projectService.openClientFile(f2.path); + closeFilesForSession([f1], session); + openFilesForSession([f2], session); // Should close configured project with next file open - projectService.closeClientFile(f2.path); - projectService.openClientFile(f3.path); - baselineTsserverLogs("projects", "loading files with correct priority", projectService); + closeFilesForSession([f2], session); + openFilesForSession([f3], session); + baselineTsserverLogs("projects", "loading files with correct priority", session); }); it("tsconfig script block support", () => { @@ -620,10 +632,9 @@ describe("unittests:: tsserver:: projects::", () => { content: jsonToReadableText({ compilerOptions: { allowJs: true } }), }; const host = createServerHost([file1, file2, config]); - const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); // HTML file will not be included in any projects yet openFilesForSession([file1], session); - const projectService = session.getProjectService(); // Specify .html extension as mixed content const extraFileExtensions = [{ extension: ".html", scriptKind: ts.ScriptKind.JS, isMixedContent: true }]; @@ -634,12 +645,17 @@ describe("unittests:: tsserver:: projects::", () => { }); // Open HTML file - projectService.applyChangesInOpenFiles(ts.singleIterator({ - fileName: file2.path, - hasMixedContent: true, - scriptKind: ts.ScriptKind.JS, - content: `var hello = "hello";`, - })); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.ApplyChangedToOpenFiles, + arguments: { + openFiles: [{ + fileName: file2.path, + hasMixedContent: true, + scriptKind: "JS", + content: `var hello = "hello";`, + }], + }, + }); // Now HTML file is included in the project // Check identifiers defined in HTML content are available in .ts file @@ -654,11 +670,12 @@ describe("unittests:: tsserver:: projects::", () => { }); // Close HTML file - projectService.applyChangesInOpenFiles( - /*openFiles*/ undefined, - /*changedFiles*/ undefined, - /*closedFiles*/ [file2.path], - ); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.ApplyChangedToOpenFiles, + arguments: { + closedFiles: [file2.path], + }, + }); // HTML file is still included in project @@ -731,7 +748,7 @@ describe("unittests:: tsserver:: projects::", () => { function verfiy(config: File, host: TestServerHost) { logger.host = host; logger.log(`currentDirectory:: ${host.getCurrentDirectory()} useCaseSensitiveFileNames: ${host.useCaseSensitiveFileNames}`); - const session = createSession(host, { logger }); + const session = new TestSession({ host, logger }); session.executeCommandSeq({ command: ts.server.protocol.CommandTypes.Configure, arguments: { extraFileExtensions }, @@ -758,19 +775,21 @@ describe("unittests:: tsserver:: projects::", () => { content: "export let x = 1", }; const host = createServerHost([file1, file2]); - const projectService = createProjectService(host, { logger: createLoggerWithInMemoryLogs(host) }); + const session = new TestSession(host); + openFilesForSession([file1, file2], session); - projectService.openClientFile(file1.path); - projectService.openClientFile(file2.path); - - projectService.applyChangesInOpenFiles( - /*openFiles*/ undefined, - /*changedFiles*/ ts.singleIterator({ fileName: file1.path, changes: ts.singleIterator({ span: ts.createTextSpan(0, file1.path.length), newText: "let y = 1" }) }), - /*closedFiles*/ undefined, - ); + session.executeCommandSeq({ + command: ts.server.protocol.CommandTypes.ApplyChangedToOpenFiles, + arguments: { + changedFiles: [{ + fileName: file1.path, + changes: [{ span: ts.createTextSpan(0, file1.path.length), newText: "let y = 1" }], + }], + }, + }); - projectService.ensureInferredProjectsUpToDate_TestOnly(); - baselineTsserverLogs("projects", "project structure update is deferred if files are not added or removed", projectService); + session.getProjectService().ensureInferredProjectsUpToDate_TestOnly(); + baselineTsserverLogs("projects", "project structure update is deferred if files are not added or removed", session); }); it("files with mixed content are handled correctly", () => { @@ -779,29 +798,37 @@ describe("unittests:: tsserver:: projects::", () => { content: `