diff --git a/.mailmap b/.mailmap index e66f669b8ee..cc101c0517e 100644 --- a/.mailmap +++ b/.mailmap @@ -1,32 +1,38 @@  -Alexander # Alexander Kuvaev AbubakerB # Abubaker Bashir +Alexander # Alexander Kuvaev Adam Freidin Adam Freidin Adi Dahiya Adi Dahiya Ahmad Farid ahmad-farid +Alexander Rusakov Alex Eagle +Anatoly Ressin Anders Hejlsberg unknown unknown +Andrej Baran Andrew Z Allen Andy Hanson Andy Anil Anar Anton Tolmachev Arnavion # Arnav Singh -Arthur Ozga Arthur Ozga +Arthur Ozga Arthur Ozga Arthur Ozga Arthur Ozga Arthur Ozga Asad Saeeduddin Schmavery # Avery Morin Basarat Ali Syed Basarat Syed basarat Bill Ticehurst Bill Ticehurst Ben Duffield +Ben Mosher Blake Embrey Bowden Kelly Brett Mayen Bryan Forbes Caitlin Potter ChrisBubernak unknown # Chris Bubernak +Christophe Vidal Chuck Jazdzewski Colby Russell Colin Snover Cyrus Najmabadi CyrusNajmabadi unknown +Dafrok # Dafrok Zhang Dan Corder Dan Quirk Dan Quirk nknown Daniel Rosenwasser Daniel Rosenwasser Daniel Rosenwasser Daniel Rosenwasser Daniel Rosenwasser @@ -36,17 +42,22 @@ Denis Nedelyaev Dick van den Brink unknown unknown Dirk Baeumer Dirk Bäumer # Dirk Bäumer Dirk Holtwick +Dom Chen Doug Ilijev Erik Edrosa +erictsangx # Eric Tsang Ethan Rubio Evan Martin Evan Sebastian Eyas # Eyas Sharaiha +Fabian Cook falsandtru # @falsandtru Frank Wallis -František Žiačik František Žiačik +František Žiacik František Žiacik +Gabe Moothart Gabriel Isenberg Gilad Peleg +Godfrey Chan Graeme Wicksted Guillaume Salles Guy Bedford guybedford @@ -55,6 +66,7 @@ Iain Monro Ingvar Stepanyan impinball # Isiah Meadows Ivo Gabe de Wolff +Jakub Młokosiewicz James Whitney Jason Freeman Jason Freeman Jason Killian @@ -69,11 +81,14 @@ Jonathan Park Jonathan Turner Jonathan Turner Jonathan Toland Jesse Schalken +Josh Abernathy joshaber Josh Kalderimis Josh Soref Juan Luis Boya García Julian Williams -Herrington Darkholme +Justin Bay +Justin Johansson +Herrington Darkholme (´·?·`) # Herrington Darkholme Kagami Sascha Rosylight SaschaNaz Kanchalai Tanglertsampan Yui Kanchalai Tanglertsampan Yui T @@ -82,23 +97,29 @@ Kanchalai Tanglertsampan Yui Kanchalai Tanglertsampan yui T Keith Mashinter kmashint Ken Howard +Kevin Lang kimamula # Kenji Imamula Kyle Kelley Lorant Pinter Lucien Greathouse +Lukas Elmer Lukas Elmer Martin Vseticka Martin Všeticka MartyIX +gcnew # Marin Marinov vvakame # Masahiro Wakame Matt McCutchen Max Deepfield Micah Zoltu +Michael Mohamed Hegazy Nathan Shively-Sanders Nathan Yee Nima Zahedi +Noah Chen Noj Vek mihailik # Oleg Mihailik Oleksandr Chekhovskyi Paul van Brenk Paul van Brenk unknown unknown unknown +Omer Sheikh Oskar Segersva¨rd pcan # Piero Cangianiello pcbro <2bux89+dk3zspjmuh16o@sharklasers.com> # @pcbro @@ -109,21 +130,26 @@ progre # @progre Prayag Verma Punya Biswal Rado Kirov -Ron Buckton Ron Buckton +Ron Buckton Ron Buckton rbuckton +Rostislav Galimsky Richard Knoll Richard Knoll Rowan Wyborn Ryan Cavanaugh Ryan Cavanaugh Ryan Cavanaugh Ryohei Ikegami Sarangan Rajamanickam Sébastien Arod +Sergey Shandar Sheetal Nandi Shengping Zhong shyyko.serhiy@gmail.com # Shyyko Serhiy +Sam El-Husseini Simon Hürlimann +Slawomir Sadziak Solal Pirelli Stan Thomas Stanislav Sysoev Steve Lucco steveluc +Sudheesh Singanamalla Tarik # Tarik Ozket Tetsuharu OHZEKI # Tetsuharu Ohzeki Tien Nguyen tien unknown #Tien Hoanhtien @@ -133,6 +159,7 @@ Tingan Ho togru # togru Tomas Grubliauskas ToddThomson # Todd Thomson +Torben Fitschen TruongSinh Tran-Nguyen vilicvane # Vilic Vane Vladimir Matveev vladima v2m @@ -140,7 +167,9 @@ Wesley Wigham Wesley Wigham York Yao york yao yaoyao Yuichi Nukiyama YuichiNukiyama Zev Spitz -Zhengbo Li zhengbli Zhengbo Li Zhengbo Li tinza123 unknown Zhengbo Li +Zhengbo Li zhengbli Zhengbo Li Zhengbo Li tinza123 unknown Zhengbo Li zhengbli zhongsp # Patrick Zhong T18970237136 # @T18970237136 -JBerger \ No newline at end of file +JBerger +bootstraponline # @bootstraponline +yortus # @yortus \ No newline at end of file diff --git a/AUTHORS.md b/AUTHORS.md index 08039127d9d..50f1ea12c2b 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -5,7 +5,10 @@ TypeScript is authored by: * Ahmad Farid * Alex Eagle * Alexander Kuvaev +* Alexander Rusakov +* Anatoly Ressin * Anders Hejlsberg +* Andrej Baran * Andrew Z Allen * Andy Hanson * Anil Anar @@ -16,17 +19,21 @@ TypeScript is authored by: * Avery Morin * Basarat Ali Syed * Ben Duffield +* Ben Mosher * Bill Ticehurst * Blake Embrey +* @bootstraponline * Bowden Kelly * Brett Mayen * Bryan Forbes * Caitlin Potter * Chris Bubernak +* Christophe Vidal * Chuck Jazdzewski * Colby Russell * Colin Snover * Cyrus Najmabadi +* Dafrok Zhang * Dan Corder * Dan Quirk * Daniel Rosenwasser @@ -36,17 +43,22 @@ TypeScript is authored by: * Dick van den Brink * Dirk Bäumer * Dirk Holtwick +* Dom Chen * Doug Ilijev +* Eric Tsang * Erik Edrosa * Ethan Rubio * Evan Martin * Evan Sebastian * Eyas Sharaiha +* Fabian Cook * @falsandtru * Frank Wallis -* František Žiačik +* František Žiacik +* Gabe Moothart * Gabriel Isenberg * Gilad Peleg +* Godfrey Chan * Graeme Wicksted * Guillaume Salles * Guy Bedford @@ -56,6 +68,7 @@ TypeScript is authored by: * Ingvar Stepanyan * Isiah Meadows * Ivo Gabe de Wolff +* Jakub Młokosiewicz * James Whitney * Jason Freeman * Jason Killian @@ -71,30 +84,39 @@ TypeScript is authored by: * Jonathan Park * Jonathan Toland * Jonathan Turner +* Josh Abernathy * Josh Kalderimis * Josh Soref * Juan Luis Boya García * Julian Williams +* Justin Bay +* Justin Johansson * Kagami Sascha Rosylight * Kanchalai Tanglertsampan * Keith Mashinter * Ken Howard * Kenji Imamula +* Kevin Lang * Kyle Kelley * Lorant Pinter * Lucien Greathouse +* Lukas Elmer +* Marin Marinov * Martin Vseticka * Masahiro Wakame * Matt McCutchen * Max Deepfield * Micah Zoltu +* Michael * Mohamed Hegazy * Nathan Shively-Sanders * Nathan Yee * Nima Zahedi +* Noah Chen * Noj Vek * Oleg Mihailik * Oleksandr Chekhovskyi +* Omer Sheikh * Oskar Segersva¨rd * Patrick Zhong * Paul van Brenk @@ -109,21 +131,27 @@ TypeScript is authored by: * Rado Kirov * Richard Knoll * Ron Buckton +* Rostislav Galimsky * Rowan Wyborn * Ryan Cavanaugh * Ryohei Ikegami +* Sam El-Husseini * Sarangan Rajamanickam +* Sergey Shandar * Sheetal Nandi * Shengping Zhong * Shyyko Serhiy * Simon Hürlimann +* Slawomir Sadziak * Solal Pirelli * Stan Thomas * Stanislav Sysoev * Steve Lucco +* Sudheesh Singanamalla * Sébastien Arod * @T18970237136 * Tarik Ozket +* Tetsuharu Ohzeki * Tien Hoanhtien * Tim Perry * Tim Viiding-Spader @@ -131,11 +159,13 @@ TypeScript is authored by: * Todd Thomson * togru * Tomas Grubliauskas +* Torben Fitschen * TruongSinh Tran-Nguyen * Vilic Vane * Vladimir Matveev * Wesley Wigham * York Yao +* @yortus * Yuichi Nukiyama * Zev Spitz * Zhengbo Li \ No newline at end of file diff --git a/Jakefile.js b/Jakefile.js index 8ce70eb353f..fc2b6359355 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -171,6 +171,7 @@ var servicesSources = [ var serverCoreSources = [ "types.d.ts", + "shared.ts", "utilities.ts", "scriptVersionCache.ts", "typingsCache.ts", @@ -193,6 +194,7 @@ var cancellationTokenSources = [ var typingsInstallerSources = [ "../types.d.ts", + "../shared.ts", "typingsInstaller.ts", "nodeTypingsInstaller.ts" ].map(function (f) { diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 4d923888e86..65c8b8e3de6 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -560,6 +560,7 @@ namespace ts { skipTransformFlagAggregation = true; bindChildrenWorker(node); skipTransformFlagAggregation = false; + subtreeTransformFlags |= node.transformFlags & ~getTransformFlagsSubtreeExclusions(node.kind); } else { const savedSubtreeTransformFlags = subtreeTransformFlags; @@ -895,8 +896,8 @@ namespace ts { const enclosingLabeledStatement = node.parent.kind === SyntaxKind.LabeledStatement ? lastOrUndefined(activeLabels) : undefined; - // if do statement is wrapped in labeled statement then target labels for break/continue with or without - // label should be the same + // if do statement is wrapped in labeled statement then target labels for break/continue with or without + // label should be the same const preConditionLabel = enclosingLabeledStatement ? enclosingLabeledStatement.continueTarget : createBranchLabel(); const postDoLabel = enclosingLabeledStatement ? enclosingLabeledStatement.breakTarget : createBranchLabel(); addAntecedent(preDoLabel, currentFlow); @@ -1234,9 +1235,11 @@ namespace ts { const postExpressionLabel = createBranchLabel(); bindCondition(node.condition, trueLabel, falseLabel); currentFlow = finishFlowLabel(trueLabel); + bind(node.questionToken); bind(node.whenTrue); addAntecedent(postExpressionLabel, currentFlow); currentFlow = finishFlowLabel(falseLabel); + bind(node.colonToken); bind(node.whenFalse); addAntecedent(postExpressionLabel, currentFlow); currentFlow = finishFlowLabel(postExpressionLabel); @@ -3208,4 +3211,65 @@ namespace ts { node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; return transformFlags & ~excludeFlags; } + + /** + * Gets the transform flags to exclude when unioning the transform flags of a subtree. + * + * NOTE: This needs to be kept up-to-date with the exclusions used in `computeTransformFlagsForNode`. + * For performance reasons, `computeTransformFlagsForNode` uses local constant values rather + * than calling this function. + */ + /* @internal */ + export function getTransformFlagsSubtreeExclusions(kind: SyntaxKind) { + if (kind >= SyntaxKind.FirstTypeNode && kind <= SyntaxKind.LastTypeNode) { + return TransformFlags.TypeExcludes; + } + + switch (kind) { + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.ArrayLiteralExpression: + return TransformFlags.ArrayLiteralOrCallOrNewExcludes; + case SyntaxKind.ModuleDeclaration: + return TransformFlags.ModuleExcludes; + case SyntaxKind.Parameter: + return TransformFlags.ParameterExcludes; + case SyntaxKind.ArrowFunction: + return TransformFlags.ArrowFunctionExcludes; + case SyntaxKind.FunctionExpression: + case SyntaxKind.FunctionDeclaration: + return TransformFlags.FunctionExcludes; + case SyntaxKind.VariableDeclarationList: + return TransformFlags.VariableDeclarationListExcludes; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + return TransformFlags.ClassExcludes; + case SyntaxKind.Constructor: + return TransformFlags.ConstructorExcludes; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return TransformFlags.MethodOrAccessorExcludes; + case SyntaxKind.AnyKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.NeverKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.TypeParameter: + case SyntaxKind.PropertySignature: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + return TransformFlags.TypeExcludes; + case SyntaxKind.ObjectLiteralExpression: + return TransformFlags.ObjectLiteralExcludes; + default: + return TransformFlags.NodeExcludes; + } + } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8349f985f58..af7dae030db 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2229,14 +2229,8 @@ namespace ts { // The specified symbol flags need to be reinterpreted as type flags buildSymbolDisplay(type.symbol, writer, enclosingDeclaration, SymbolFlags.Type, SymbolFormatFlags.None, nextFlags); } - else if (!(flags & TypeFormatFlags.InTypeAlias) && ((getObjectFlags(type) & ObjectFlags.Anonymous && !(type).target) || type.flags & TypeFlags.UnionOrIntersection) && type.aliasSymbol && + else if (!(flags & TypeFormatFlags.InTypeAlias) && (getObjectFlags(type) & ObjectFlags.Anonymous || type.flags & TypeFlags.UnionOrIntersection) && type.aliasSymbol && isSymbolAccessible(type.aliasSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false).accessibility === SymbolAccessibility.Accessible) { - // We emit inferred type as type-alias at the current localtion if all the following is true - // the input type is has alias symbol that is accessible - // the input type is a union, intersection or anonymous type that is fully instantiated (if not we want to keep dive into) - // e.g.: export type Bar = () => [X, Y]; - // export type Foo = Bar; - // export const y = (x: Foo) => 1 // we want to emit as ...x: () => [any, string]) const typeArguments = type.aliasTypeArguments; writeSymbolTypeReference(type.aliasSymbol, typeArguments, 0, typeArguments ? typeArguments.length : 0, nextFlags); } @@ -4198,8 +4192,8 @@ namespace ts { else { mapper = createTypeMapper(typeParameters, typeArguments); members = createInstantiatedSymbolTable(source.declaredProperties, mapper, /*mappingThisOnly*/ typeParameters.length === 1); - callSignatures = instantiateList(source.declaredCallSignatures, mapper, instantiateSignature); - constructSignatures = instantiateList(source.declaredConstructSignatures, mapper, instantiateSignature); + callSignatures = instantiateSignatures(source.declaredCallSignatures, mapper); + constructSignatures = instantiateSignatures(source.declaredConstructSignatures, mapper); stringIndexInfo = instantiateIndexInfo(source.declaredStringIndexInfo, mapper); numberIndexInfo = instantiateIndexInfo(source.declaredNumberIndexInfo, mapper); } @@ -4266,7 +4260,7 @@ namespace ts { for (const baseSig of baseSignatures) { const typeParamCount = baseSig.typeParameters ? baseSig.typeParameters.length : 0; if (typeParamCount === typeArgCount) { - const sig = typeParamCount ? getSignatureInstantiation(baseSig, typeArguments) : cloneSignature(baseSig); + const sig = typeParamCount ? createSignatureInstantiation(baseSig, typeArguments) : cloneSignature(baseSig); sig.typeParameters = classType.localTypeParameters; sig.resolvedReturnType = classType; result.push(sig); @@ -4397,8 +4391,8 @@ namespace ts { const symbol = type.symbol; if (type.target) { const members = createInstantiatedSymbolTable(getPropertiesOfObjectType(type.target), type.mapper, /*mappingThisOnly*/ false); - const callSignatures = instantiateList(getSignaturesOfType(type.target, SignatureKind.Call), type.mapper, instantiateSignature); - const constructSignatures = instantiateList(getSignaturesOfType(type.target, SignatureKind.Construct), type.mapper, instantiateSignature); + const callSignatures = instantiateSignatures(getSignaturesOfType(type.target, SignatureKind.Call), type.mapper); + const constructSignatures = instantiateSignatures(getSignaturesOfType(type.target, SignatureKind.Construct), type.mapper); const stringIndexInfo = instantiateIndexInfo(getIndexInfoOfType(type.target, IndexKind.String), type.mapper); const numberIndexInfo = instantiateIndexInfo(getIndexInfoOfType(type.target, IndexKind.Number), type.mapper); setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); @@ -5071,6 +5065,12 @@ namespace ts { } function getSignatureInstantiation(signature: Signature, typeArguments: Type[]): Signature { + const instantiations = signature.instantiations || (signature.instantiations = createMap()); + const id = getTypeListId(typeArguments); + return instantiations[id] || (instantiations[id] = createSignatureInstantiation(signature, typeArguments)); + } + + function createSignatureInstantiation(signature: Signature, typeArguments: Type[]): Signature { return instantiateSignature(signature, createTypeMapper(signature.typeParameters, typeArguments), /*eraseTypeParameters*/ true); } @@ -6155,6 +6155,14 @@ namespace ts { return items; } + function instantiateTypes(types: Type[], mapper: TypeMapper) { + return instantiateList(types, mapper, instantiateType); + } + + function instantiateSignatures(signatures: Signature[], mapper: TypeMapper) { + return instantiateList(signatures, mapper, instantiateSignature); + } + function instantiateCached(type: T, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): T { const instantiations = mapper.instantiations || (mapper.instantiations = []); return instantiations[type.id] || (instantiations[type.id] = instantiator(type, mapper)); @@ -6186,7 +6194,6 @@ namespace ts { count == 2 ? createBinaryTypeMapper(sources[0], targets ? targets[0] : anyType, sources[1], targets ? targets[1] : anyType) : createArrayTypeMapper(sources, targets); mapper.mappedTypes = sources; - mapper.targetTypes = targets; return mapper; } @@ -6303,7 +6310,7 @@ namespace ts { result.target = type.objectFlags & ObjectFlags.Instantiated ? type.target : type; result.mapper = type.objectFlags & ObjectFlags.Instantiated ? combineTypeMappers(type.mapper, mapper) : mapper; result.aliasSymbol = type.aliasSymbol; - result.aliasTypeArguments = mapper.targetTypes; + result.aliasTypeArguments = instantiateTypes(type.aliasTypeArguments, mapper); return result; } @@ -6315,7 +6322,7 @@ namespace ts { result.isOptional = type.isOptional; result.mapper = type.objectFlags & ObjectFlags.Instantiated ? combineTypeMappers(type.mapper, mapper) : mapper; result.aliasSymbol = type.aliasSymbol; - result.aliasTypeArguments = mapper.targetTypes; + result.aliasTypeArguments = instantiateTypes(type.aliasTypeArguments, mapper); return result; } @@ -6413,14 +6420,14 @@ namespace ts { return instantiateCached(type, mapper, instantiateMappedType); } if ((type).objectFlags & ObjectFlags.Reference) { - return createTypeReference((type).target, instantiateList((type).typeArguments, mapper, instantiateType)); + return createTypeReference((type).target, instantiateTypes((type).typeArguments, mapper)); } } if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Primitive)) { - return getUnionType(instantiateList((type).types, mapper, instantiateType), /*subtypeReduction*/ false, type.aliasSymbol, mapper.targetTypes); + return getUnionType(instantiateTypes((type).types, mapper), /*subtypeReduction*/ false, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)); } if (type.flags & TypeFlags.Intersection) { - return getIntersectionType(instantiateList((type).types, mapper, instantiateType), type.aliasSymbol, mapper.targetTypes); + return getIntersectionType(instantiateTypes((type).types, mapper), type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)); } if (type.flags & TypeFlags.Index) { return getIndexType(instantiateType((type).type, mapper)); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index dcb9cce0adb..1b36d99d7c9 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -420,6 +420,7 @@ namespace ts { getTypeCount: () => getDiagnosticsProducingTypeChecker().getTypeCount(), getFileProcessingDiagnostics: () => fileProcessingDiagnostics, getResolvedTypeReferenceDirectives: () => resolvedTypeReferenceDirectives, + isSourceFileFromExternalLibrary, dropDiagnosticsProducingTypeChecker }; @@ -722,13 +723,17 @@ namespace ts { getSourceFile: program.getSourceFile, getSourceFileByPath: program.getSourceFileByPath, getSourceFiles: program.getSourceFiles, - isSourceFileFromExternalLibrary: (file: SourceFile) => !!sourceFilesFoundSearchingNodeModules[file.path], + isSourceFileFromExternalLibrary, writeFile: writeFileCallback || ( (fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)), isEmitBlocked, }; } + function isSourceFileFromExternalLibrary(file: SourceFile): boolean { + return sourceFilesFoundSearchingNodeModules[file.path]; + } + function getDiagnosticsProducingTypeChecker() { return diagnosticsProducingTypeChecker || (diagnosticsProducingTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ true)); } diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 13bbfc2ab15..a1e82a66595 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -17,7 +17,11 @@ namespace ts { readFile(path: string, encoding?: string): string; getFileSize?(path: string): number; writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; - watchFile?(path: string, callback: FileWatcherCallback): FileWatcher; + /** + * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that + * use native OS file watching + */ + watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher; resolvePath(path: string): string; fileExists(path: string): boolean; @@ -439,6 +443,7 @@ namespace ts { return filter(_fs.readdirSync(path), dir => fileSystemEntryExists(combinePaths(path, dir), FileSystemEntryKind.Directory)); } + const noOpFileWatcher: FileWatcher = { close: noop }; const nodeSystem: System = { args: process.argv.slice(2), newLine: _os.EOL, @@ -448,7 +453,7 @@ namespace ts { }, readFile, writeFile, - watchFile: (fileName, callback) => { + watchFile: (fileName, callback, pollingInterval) => { if (useNonPollingWatchers) { const watchedFile = watchedFileSet.addFile(fileName, callback); return { @@ -456,7 +461,7 @@ namespace ts { }; } else { - _fs.watchFile(fileName, { persistent: true, interval: 250 }, fileChanged); + _fs.watchFile(fileName, { persistent: true, interval: pollingInterval || 250 }, fileChanged); return { close: () => _fs.unwatchFile(fileName, fileChanged) }; @@ -475,7 +480,7 @@ namespace ts { // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) let options: any; if (!directoryExists(directoryName)) { - return; + return noOpFileWatcher; } if (isNode4OrLater() && (process.platform === "win32" || process.platform === "darwin")) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7bea81ef6b6..99d776650b4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2202,6 +2202,7 @@ namespace ts { /* @internal */ getFileProcessingDiagnostics(): DiagnosticCollection; /* @internal */ getResolvedTypeReferenceDirectives(): Map; + /* @internal */ isSourceFileFromExternalLibrary(file: SourceFile): boolean; // For testing purposes only. /* @internal */ structureIsReused?: boolean; } @@ -2944,6 +2945,8 @@ namespace ts { isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison /* @internal */ typePredicate?: TypePredicate; + /* @internal */ + instantiations?: Map; // Generic signature instantiation cache } export const enum IndexKind { @@ -2961,7 +2964,6 @@ namespace ts { export interface TypeMapper { (t: TypeParameter): Type; mappedTypes?: Type[]; // Types mapped by this mapper - targetTypes?: Type[]; // Types substituted for mapped types instantiations?: Type[]; // Cache of instantiations created using this type mapper. context?: InferenceContext; // The inference context this mapper was created from. // Only inference mappers have this set (in createInferenceMapper). diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 45b82a6a08a..34cc2e07436 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -1270,66 +1270,6 @@ namespace ts { return transformFlags | aggregateTransformFlagsForNode(child); } - /** - * Gets the transform flags to exclude when unioning the transform flags of a subtree. - * - * NOTE: This needs to be kept up-to-date with the exclusions used in `computeTransformFlagsForNode`. - * For performance reasons, `computeTransformFlagsForNode` uses local constant values rather - * than calling this function. - */ - function getTransformFlagsSubtreeExclusions(kind: SyntaxKind) { - if (kind >= SyntaxKind.FirstTypeNode && kind <= SyntaxKind.LastTypeNode) { - return TransformFlags.TypeExcludes; - } - - switch (kind) { - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - case SyntaxKind.ArrayLiteralExpression: - return TransformFlags.ArrayLiteralOrCallOrNewExcludes; - case SyntaxKind.ModuleDeclaration: - return TransformFlags.ModuleExcludes; - case SyntaxKind.Parameter: - return TransformFlags.ParameterExcludes; - case SyntaxKind.ArrowFunction: - return TransformFlags.ArrowFunctionExcludes; - case SyntaxKind.FunctionExpression: - case SyntaxKind.FunctionDeclaration: - return TransformFlags.FunctionExcludes; - case SyntaxKind.VariableDeclarationList: - return TransformFlags.VariableDeclarationListExcludes; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - return TransformFlags.ClassExcludes; - case SyntaxKind.Constructor: - return TransformFlags.ConstructorExcludes; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return TransformFlags.MethodOrAccessorExcludes; - case SyntaxKind.AnyKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.BooleanKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.VoidKeyword: - case SyntaxKind.TypeParameter: - case SyntaxKind.PropertySignature: - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - return TransformFlags.TypeExcludes; - case SyntaxKind.ObjectLiteralExpression: - return TransformFlags.ObjectLiteralExcludes; - default: - return TransformFlags.NodeExcludes; - } - } - export namespace Debug { export const failNotOptional = shouldAssert(AssertionLevel.Normal) ? (message?: string) => assert(false, message || "Node not optional.") diff --git a/src/harness/unittests/incrementalParser.ts b/src/harness/unittests/incrementalParser.ts index 0082207e699..6088e5fe082 100644 --- a/src/harness/unittests/incrementalParser.ts +++ b/src/harness/unittests/incrementalParser.ts @@ -44,10 +44,10 @@ namespace ts { // NOTE: 'reusedElements' is the expected count of elements reused from the old tree to the new // tree. It may change as we tweak the parser. If the count increases then that should always - // be a good thing. If it decreases, that's not great (less reusability), but that may be - // unavoidable. If it does decrease an investigation should be done to make sure that things + // be a good thing. If it decreases, that's not great (less reusability), but that may be + // unavoidable. If it does decrease an investigation should be done to make sure that things // are still ok and we're still appropriately reusing most of the tree. - function compareTrees(oldText: IScriptSnapshot, newText: IScriptSnapshot, textChangeRange: TextChangeRange, expectedReusedElements: number, oldTree?: SourceFile): SourceFile { + function compareTrees(oldText: IScriptSnapshot, newText: IScriptSnapshot, textChangeRange: TextChangeRange, expectedReusedElements: number, oldTree?: SourceFile) { oldTree = oldTree || createTree(oldText, /*version:*/ "."); Utils.assertInvariants(oldTree, /*parent:*/ undefined); @@ -76,7 +76,7 @@ namespace ts { assert.equal(actualReusedCount, expectedReusedElements, actualReusedCount + " !== " + expectedReusedElements); } - return incrementalNewTree; + return { oldTree, newTree, incrementalNewTree }; } function reusedElements(oldNode: SourceFile, newNode: SourceFile): number { @@ -103,7 +103,7 @@ namespace ts { for (let i = 0; i < repeat; i++) { const oldText = ScriptSnapshot.fromString(source); const newTextAndChange = withDelete(oldText, index, 1); - const newTree = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1, oldTree); + const newTree = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1, oldTree).incrementalNewTree; source = newTextAndChange.text.getText(0, newTextAndChange.text.getLength()); oldTree = newTree; @@ -116,7 +116,7 @@ namespace ts { for (let i = 0; i < repeat; i++) { const oldText = ScriptSnapshot.fromString(source); const newTextAndChange = withInsert(oldText, index + i, toInsert.charAt(i)); - const newTree = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1, oldTree); + const newTree = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1, oldTree).incrementalNewTree; source = newTextAndChange.text.getText(0, newTextAndChange.text.getLength()); oldTree = newTree; @@ -639,7 +639,7 @@ module m3 { }\ }); it("Unterminated comment after keyword converted to identifier", () => { - // 'public' as a keyword should be incrementally unusable (because it has an + // 'public' as a keyword should be incrementally unusable (because it has an // unterminated comment). When we convert it to an identifier, that shouldn't // change anything, and we should still get the same errors. const source = "return; a.public /*"; @@ -796,6 +796,16 @@ module m3 { }\ compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); }); + it("Reuse transformFlags of subtree during bind", () => { + const source = `class Greeter { constructor(element: HTMLElement) { } }`; + const oldText = ScriptSnapshot.fromString(source); + const newTextAndChange = withChange(oldText, 15, 0, "\n"); + const { oldTree, incrementalNewTree } = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1); + bindSourceFile(oldTree, {}); + bindSourceFile(incrementalNewTree, {}); + assert.equal(oldTree.transformFlags, incrementalNewTree.transformFlags); + }); + // Simulated typing tests. it("Type extends clause 1", () => { diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 15cfa3f8c8e..62c0957d6a3 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -18,7 +18,6 @@ namespace ts.projectSystem { }; export interface PostExecAction { - readonly requestKind: TI.RequestKind; readonly success: boolean; readonly callback: TI.RequestCompletedAction; } @@ -47,9 +46,14 @@ namespace ts.projectSystem { export class TestTypingsInstaller extends TI.TypingsInstaller implements server.ITypingsInstaller { protected projectService: server.ProjectService; - constructor(readonly globalTypingsCacheLocation: string, throttleLimit: number, readonly installTypingHost: server.ServerHost, log?: TI.Log) { - super(globalTypingsCacheLocation, safeList.path, throttleLimit, log); - this.init(); + constructor( + readonly globalTypingsCacheLocation: string, + throttleLimit: number, + installTypingHost: server.ServerHost, + readonly typesRegistry = createMap(), + telemetryEnabled?: boolean, + log?: TI.Log) { + super(installTypingHost, globalTypingsCacheLocation, safeList.path, throttleLimit, telemetryEnabled, log); } safeFileList = safeList.path; @@ -63,9 +67,8 @@ namespace ts.projectSystem { } } - checkPendingCommands(expected: TI.RequestKind[]) { - assert.equal(this.postExecActions.length, expected.length, `Expected ${expected.length} post install actions`); - this.postExecActions.forEach((act, i) => assert.equal(act.requestKind, expected[i], "Unexpected post install action")); + checkPendingCommands(expectedCount: number) { + assert.equal(this.postExecActions.length, expectedCount, `Expected ${expectedCount} post install actions`); } onProjectClosed() { @@ -79,15 +82,8 @@ namespace ts.projectSystem { return this.installTypingHost; } - executeRequest(requestKind: TI.RequestKind, _requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { - switch (requestKind) { - case TI.NpmViewRequest: - case TI.NpmInstallRequest: - break; - default: - assert.isTrue(false, `request ${requestKind} is not supported`); - } - this.addPostExecAction(requestKind, "success", cb); + installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { + this.addPostExecAction("success", cb); } sendResponse(response: server.SetTypings | server.InvalidateCachedTypings) { @@ -99,12 +95,11 @@ namespace ts.projectSystem { this.install(request); } - addPostExecAction(requestKind: TI.RequestKind, stdout: string | string[], cb: TI.RequestCompletedAction) { + addPostExecAction(stdout: string | string[], cb: TI.RequestCompletedAction) { const out = typeof stdout === "string" ? stdout : createNpmPackageJsonString(stdout); const action: PostExecAction = { success: !!out, - callback: cb, - requestKind + callback: cb }; this.postExecActions.push(action); } @@ -1969,6 +1964,33 @@ namespace ts.projectSystem { projectService.checkNumberOfProjects({ configuredProjects: 1 }); checkProjectActualFiles(projectService.configuredProjects[0], [libES5.path, libES2015Promise.path, app.path]); }); + + it("should handle non-existing directories in config file", () => { + const f = { + path: "/a/src/app.ts", + content: "let x = 1;" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: {}, + include: [ + "src/**/*", + "notexistingfolder/*" + ] + }) + }; + const host = createServerHost([f, config]); + const projectService = createProjectService(host); + projectService.openClientFile(f.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + + projectService.closeClientFile(f.path); + projectService.checkNumberOfProjects({ configuredProjects: 0 }); + + projectService.openClientFile(f.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + }); }); describe("prefer typings to js", () => { @@ -2227,6 +2249,27 @@ namespace ts.projectSystem { assert.equal(diags.length, 0); }); + it("should property handle missing config files", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/b/tsconfig.json", + content: "{}" + }; + const projectName = "project1"; + const host = createServerHost([f1]); + const projectService = createProjectService(host); + projectService.openExternalProject({ rootFiles: toExternalFiles([f1.path, config.path]), options: {}, projectFileName: projectName }); + + // should have one external project since config file is missing + projectService.checkNumberOfProjects({ externalProjects: 1 }); + + host.reloadFS([f1, config]); + projectService.openExternalProject({ rootFiles: toExternalFiles([f1.path, config.path]), options: {}, projectFileName: projectName }); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + }); }); describe("add the missing module file for inferred project", () => { diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index 356992d5717..2dda506ad58 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -8,44 +8,44 @@ namespace ts.projectSystem { interface InstallerParams { globalTypingsCacheLocation?: string; throttleLimit?: number; + typesRegistry?: Map; + } + + function createTypesRegistry(...list: string[]): Map { + const map = createMap(); + for (const l of list) { + map[l] = undefined; + } + return map; } class Installer extends TestTypingsInstaller { - constructor(host: server.ServerHost, p?: InstallerParams, log?: TI.Log) { + constructor(host: server.ServerHost, p?: InstallerParams, telemetryEnabled?: boolean, log?: TI.Log) { super( (p && p.globalTypingsCacheLocation) || "/a/data", (p && p.throttleLimit) || 5, host, + (p && p.typesRegistry), + telemetryEnabled, log); } - installAll(expectedView: typeof TI.NpmViewRequest[], expectedInstall: typeof TI.NpmInstallRequest[]) { - this.checkPendingCommands(expectedView); - this.executePendingCommands(); - this.checkPendingCommands(expectedInstall); + installAll(expectedCount: number) { + this.checkPendingCommands(expectedCount); this.executePendingCommands(); } } - describe("typingsInstaller", () => { - function executeCommand(self: Installer, host: TestServerHost, installedTypings: string[], typingFiles: FileOrFolder[], requestKind: TI.RequestKind, cb: TI.RequestCompletedAction): void { - switch (requestKind) { - case TI.NpmInstallRequest: - self.addPostExecAction(requestKind, installedTypings, success => { - for (const file of typingFiles) { - host.createFileOrFolder(file, /*createParentDirectory*/ true); - } - cb(success); - }); - break; - case TI.NpmViewRequest: - self.addPostExecAction(requestKind, installedTypings, cb); - break; - default: - assert.isTrue(false, `unexpected request kind ${requestKind}`); - break; + function executeCommand(self: Installer, host: TestServerHost, installedTypings: string[], typingFiles: FileOrFolder[], cb: TI.RequestCompletedAction): void { + self.addPostExecAction(installedTypings, success => { + for (const file of typingFiles) { + host.createFileOrFolder(file, /*createParentDirectory*/ true); } - } + cb(success); + }); + } + + describe("typingsInstaller", () => { it("configured projects (typings installed) 1", () => { const file1 = { path: "/a/b/app.js", @@ -79,12 +79,12 @@ namespace ts.projectSystem { const host = createServerHost([file1, tsconfig, packageJson]); const installer = new (class extends Installer { constructor() { - super(host); + super(host, { typesRegistry: createTypesRegistry("jquery") }); } - executeRequest(requestKind: TI.RequestKind, _requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) { + installWorker(_requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) { const installedTypings = ["@types/jquery"]; const typingFiles = [jquery]; - executeCommand(this, host, installedTypings, typingFiles, requestKind, cb); + executeCommand(this, host, installedTypings, typingFiles, cb); } })(); @@ -95,7 +95,7 @@ namespace ts.projectSystem { const p = projectService.configuredProjects[0]; checkProjectActualFiles(p, [file1.path]); - installer.installAll([TI.NpmViewRequest], [TI.NpmInstallRequest]); + installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { configuredProjects: 1 }); checkProjectActualFiles(p, [file1.path, jquery.path]); @@ -123,12 +123,12 @@ namespace ts.projectSystem { const host = createServerHost([file1, packageJson]); const installer = new (class extends Installer { constructor() { - super(host); + super(host, { typesRegistry: createTypesRegistry("jquery") }); } - executeRequest(requestKind: TI.RequestKind, _requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) { + installWorker(_requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) { const installedTypings = ["@types/jquery"]; const typingFiles = [jquery]; - executeCommand(this, host, installedTypings, typingFiles, requestKind, cb); + executeCommand(this, host, installedTypings, typingFiles, cb); } })(); @@ -139,7 +139,7 @@ namespace ts.projectSystem { const p = projectService.inferredProjects[0]; checkProjectActualFiles(p, [file1.path]); - installer.installAll([TI.NpmViewRequest], [TI.NpmInstallRequest]); + installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { inferredProjects: 1 }); checkProjectActualFiles(p, [file1.path, jquery.path]); @@ -167,7 +167,7 @@ namespace ts.projectSystem { options: {}, rootFiles: [toExternalFile(file1.path)] }); - installer.checkPendingCommands([]); + installer.checkPendingCommands(/*expectedCount*/ 0); // by default auto discovery will kick in if project contain only .js/.d.ts files // in this case project contain only ts files - no auto discovery projectService.checkNumberOfProjects({ externalProjects: 1 }); @@ -181,7 +181,7 @@ namespace ts.projectSystem { const host = createServerHost([file1]); const installer = new (class extends Installer { constructor() { - super(host); + super(host, { typesRegistry: createTypesRegistry("jquery") }); } enqueueInstallTypingsRequest() { assert(false, "auto discovery should not be enabled"); @@ -196,7 +196,7 @@ namespace ts.projectSystem { rootFiles: [toExternalFile(file1.path)], typingOptions: { include: ["jquery"] } }); - installer.checkPendingCommands([]); + installer.checkPendingCommands(/*expectedCount*/ 0); // by default auto discovery will kick in if project contain only .js/.d.ts files // in this case project contain only ts files - no auto discovery even if typing options is set projectService.checkNumberOfProjects({ externalProjects: 1 }); @@ -215,16 +215,16 @@ namespace ts.projectSystem { let enqueueIsCalled = false; const installer = new (class extends Installer { constructor() { - super(host); + super(host, { typesRegistry: createTypesRegistry("jquery") }); } enqueueInstallTypingsRequest(project: server.Project, typingOptions: TypingOptions, unresolvedImports: server.SortedReadonlyArray) { enqueueIsCalled = true; super.enqueueInstallTypingsRequest(project, typingOptions, unresolvedImports); } - executeRequest(requestKind: TI.RequestKind, _requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { - const installedTypings = ["@types/jquery"]; + installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { + const installedTypings = ["@types/node"]; const typingFiles = [jquery]; - executeCommand(this, host, installedTypings, typingFiles, requestKind, cb); + executeCommand(this, host, installedTypings, typingFiles, cb); } })(); @@ -234,11 +234,11 @@ namespace ts.projectSystem { projectFileName, options: {}, rootFiles: [toExternalFile(file1.path)], - typingOptions: { enableAutoDiscovery: true, include: ["node"] } + typingOptions: { enableAutoDiscovery: true, include: ["jquery"] } }); assert.isTrue(enqueueIsCalled, "expected enqueueIsCalled to be true"); - installer.installAll([TI.NpmViewRequest], [TI.NpmInstallRequest]); + installer.installAll(/*expectedCount*/ 1); // autoDiscovery is set in typing options - use it even if project contains only .ts files projectService.checkNumberOfProjects({ externalProjects: 1 }); @@ -273,12 +273,12 @@ namespace ts.projectSystem { const host = createServerHost([file1, file2, file3]); const installer = new (class extends Installer { constructor() { - super(host); + super(host, { typesRegistry: createTypesRegistry("lodash", "react") }); } - executeRequest(requestKind: TI.RequestKind, _requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { + installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { const installedTypings = ["@types/lodash", "@types/react"]; const typingFiles = [lodash, react]; - executeCommand(this, host, installedTypings, typingFiles, requestKind, cb); + executeCommand(this, host, installedTypings, typingFiles, cb); } })(); @@ -295,7 +295,7 @@ namespace ts.projectSystem { projectService.checkNumberOfProjects({ externalProjects: 1 }); checkProjectActualFiles(p, [file1.path, file2.path, file3.path]); - installer.installAll([TI.NpmViewRequest, TI.NpmViewRequest], [TI.NpmInstallRequest], ); + installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { externalProjects: 1 }); checkProjectActualFiles(p, [file1.path, file2.path, file3.path, lodash.path, react.path]); @@ -317,16 +317,16 @@ namespace ts.projectSystem { let enqueueIsCalled = false; const installer = new (class extends Installer { constructor() { - super(host); + super(host, { typesRegistry: createTypesRegistry("jquery") }); } enqueueInstallTypingsRequest(project: server.Project, typingOptions: TypingOptions, unresolvedImports: server.SortedReadonlyArray) { enqueueIsCalled = true; super.enqueueInstallTypingsRequest(project, typingOptions, unresolvedImports); } - executeRequest(requestKind: TI.RequestKind, _requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { + installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { const installedTypings: string[] = []; const typingFiles: FileOrFolder[] = []; - executeCommand(this, host, installedTypings, typingFiles, requestKind, cb); + executeCommand(this, host, installedTypings, typingFiles, cb); } })(); @@ -343,7 +343,7 @@ namespace ts.projectSystem { projectService.checkNumberOfProjects({ externalProjects: 1 }); checkProjectActualFiles(p, [file1.path, file2.path]); - installer.checkPendingCommands([]); + installer.checkPendingCommands(/*expectedCount*/ 0); checkNumberOfProjects(projectService, { externalProjects: 1 }); checkProjectActualFiles(p, [file1.path, file2.path]); @@ -396,12 +396,12 @@ namespace ts.projectSystem { const host = createServerHost([file1, file2, file3, packageJson]); const installer = new (class extends Installer { constructor() { - super(host); + super(host, { typesRegistry: createTypesRegistry("jquery", "commander", "moment", "express") }); } - executeRequest(requestKind: TI.RequestKind, _requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { + installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { const installedTypings = ["@types/commander", "@types/express", "@types/jquery", "@types/moment"]; const typingFiles = [commander, express, jquery, moment]; - executeCommand(this, host, installedTypings, typingFiles, requestKind, cb); + executeCommand(this, host, installedTypings, typingFiles, cb); } })(); @@ -418,10 +418,7 @@ namespace ts.projectSystem { projectService.checkNumberOfProjects({ externalProjects: 1 }); checkProjectActualFiles(p, [file1.path, file2.path, file3.path]); - installer.installAll( - [TI.NpmViewRequest, TI.NpmViewRequest, TI.NpmViewRequest, TI.NpmViewRequest], - [TI.NpmInstallRequest] - ); + installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { externalProjects: 1 }); checkProjectActualFiles(p, [file1.path, file2.path, file3.path, commander.path, express.path, jquery.path, moment.path]); @@ -475,11 +472,11 @@ namespace ts.projectSystem { const host = createServerHost([lodashJs, commanderJs, file3, packageJson]); const installer = new (class extends Installer { constructor() { - super(host, { throttleLimit: 3 }); + super(host, { throttleLimit: 3, typesRegistry: createTypesRegistry("commander", "express", "jquery", "moment", "lodash") }); } - executeRequest(requestKind: TI.RequestKind, _requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { + installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { const installedTypings = ["@types/commander", "@types/express", "@types/jquery", "@types/moment", "@types/lodash"]; - executeCommand(this, host, installedTypings, typingFiles, requestKind, cb); + executeCommand(this, host, installedTypings, typingFiles, cb); } })(); @@ -495,18 +492,7 @@ namespace ts.projectSystem { const p = projectService.externalProjects[0]; projectService.checkNumberOfProjects({ externalProjects: 1 }); checkProjectActualFiles(p, [lodashJs.path, commanderJs.path, file3.path]); - // expected 3 view requests in the queue - installer.checkPendingCommands([TI.NpmViewRequest, TI.NpmViewRequest, TI.NpmViewRequest]); - assert.equal(installer.pendingRunRequests.length, 2, "expected 2 pending requests"); - - // push view requests - installer.executePendingCommands(); - // expected 2 remaining view requests in the queue - installer.checkPendingCommands([TI.NpmViewRequest, TI.NpmViewRequest]); - // push view requests - installer.executePendingCommands(); - // expected one install request - installer.checkPendingCommands([TI.NpmInstallRequest]); + installer.checkPendingCommands(/*expectedCount*/ 1); installer.executePendingCommands(); // expected all typings file to exist for (const f of typingFiles) { @@ -565,22 +551,17 @@ namespace ts.projectSystem { const host = createServerHost([lodashJs, commanderJs, file3]); const installer = new (class extends Installer { constructor() { - super(host, { throttleLimit: 3 }); + super(host, { throttleLimit: 1, typesRegistry: createTypesRegistry("commander", "jquery", "lodash", "cordova", "gulp", "grunt") }); } - executeRequest(requestKind: TI.RequestKind, _requestId: number, args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { - if (requestKind === TI.NpmInstallRequest) { - let typingFiles: (FileOrFolder & { typings: string })[] = []; - if (args.indexOf("@types/commander") >= 0) { - typingFiles = [commander, jquery, lodash, cordova]; - } - else { - typingFiles = [grunt, gulp]; - } - executeCommand(this, host, typingFiles.map(f => f.typings), typingFiles, requestKind, cb); + installWorker(_requestId: number, args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { + let typingFiles: (FileOrFolder & { typings: string })[] = []; + if (args.indexOf("@types/commander") >= 0) { + typingFiles = [commander, jquery, lodash, cordova]; } else { - executeCommand(this, host, [], [], requestKind, cb); + typingFiles = [grunt, gulp]; } + executeCommand(this, host, typingFiles.map(f => f.typings), typingFiles, cb); } })(); @@ -594,8 +575,8 @@ namespace ts.projectSystem { typingOptions: { include: ["jquery", "cordova"] } }); - installer.checkPendingCommands([TI.NpmViewRequest, TI.NpmViewRequest, TI.NpmViewRequest]); - assert.equal(installer.pendingRunRequests.length, 1, "expect one throttled request"); + installer.checkPendingCommands(/*expectedCount*/ 1); + assert.equal(installer.pendingRunRequests.length, 0, "expect no throttled requests"); // Create project #2 with 2 typings const projectFileName2 = "/a/app/test2.csproj"; @@ -605,7 +586,7 @@ namespace ts.projectSystem { rootFiles: [toExternalFile(file3.path)], typingOptions: { include: ["grunt", "gulp"] } }); - assert.equal(installer.pendingRunRequests.length, 3, "expect three throttled request"); + assert.equal(installer.pendingRunRequests.length, 1, "expect one throttled request"); const p1 = projectService.externalProjects[0]; const p2 = projectService.externalProjects[1]; @@ -613,18 +594,14 @@ namespace ts.projectSystem { checkProjectActualFiles(p1, [lodashJs.path, commanderJs.path, file3.path]); checkProjectActualFiles(p2, [file3.path]); - installer.executePendingCommands(); - // expected one view request from the first project and two - from the second one - installer.checkPendingCommands([TI.NpmViewRequest, TI.NpmViewRequest, TI.NpmViewRequest]); + + // expected one install request from the second project + installer.checkPendingCommands(/*expectedCount*/ 1); assert.equal(installer.pendingRunRequests.length, 0, "expected no throttled requests"); installer.executePendingCommands(); - // should be two install requests from both projects - installer.checkPendingCommands([TI.NpmInstallRequest, TI.NpmInstallRequest]); - installer.executePendingCommands(); - checkProjectActualFiles(p1, [lodashJs.path, commanderJs.path, file3.path, commander.path, jquery.path, lodash.path, cordova.path]); checkProjectActualFiles(p2, [file3.path, grunt.path, gulp.path]); }); @@ -653,12 +630,12 @@ namespace ts.projectSystem { const host = createServerHost([app, jsconfig, jquery, jqueryPackage]); const installer = new (class extends Installer { constructor() { - super(host, { globalTypingsCacheLocation: "/tmp" }); + super(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("jquery") }); } - executeRequest(requestKind: TI.RequestKind, _requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) { + installWorker(_requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) { const installedTypings = ["@types/jquery"]; const typingFiles = [jqueryDTS]; - executeCommand(this, host, installedTypings, typingFiles, requestKind, cb); + executeCommand(this, host, installedTypings, typingFiles, cb); } })(); @@ -669,7 +646,7 @@ namespace ts.projectSystem { const p = projectService.configuredProjects[0]; checkProjectActualFiles(p, [app.path]); - installer.installAll([TI.NpmViewRequest], [TI.NpmInstallRequest]); + installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { configuredProjects: 1 }); checkProjectActualFiles(p, [app.path, jqueryDTS.path]); @@ -699,12 +676,12 @@ namespace ts.projectSystem { const host = createServerHost([app, jsconfig, bowerJson]); const installer = new (class extends Installer { constructor() { - super(host, { globalTypingsCacheLocation: "/tmp" }); + super(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("jquery") }); } - executeRequest(requestKind: TI.RequestKind, _requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) { + installWorker(_requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) { const installedTypings = ["@types/jquery"]; const typingFiles = [jqueryDTS]; - executeCommand(this, host, installedTypings, typingFiles, requestKind, cb); + executeCommand(this, host, installedTypings, typingFiles, cb); } })(); @@ -715,7 +692,7 @@ namespace ts.projectSystem { const p = projectService.configuredProjects[0]; checkProjectActualFiles(p, [app.path]); - installer.installAll([TI.NpmViewRequest], [TI.NpmInstallRequest]); + installer.installAll(/*expectedCount*/ 1); checkNumberOfProjects(projectService, { configuredProjects: 1 }); checkProjectActualFiles(p, [app.path, jqueryDTS.path]); @@ -742,23 +719,23 @@ namespace ts.projectSystem { const host = createServerHost([f, brokenPackageJson]); const installer = new (class extends Installer { constructor() { - super(host, { globalTypingsCacheLocation: cachePath }); + super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") }); } - executeRequest(requestKind: TI.RequestKind, _requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) { + installWorker(_requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) { const installedTypings = ["@types/commander"]; const typingFiles = [commander]; - executeCommand(this, host, installedTypings, typingFiles, requestKind, cb); + executeCommand(this, host, installedTypings, typingFiles, cb); } })(); const service = createProjectService(host, { typingsInstaller: installer }); service.openClientFile(f.path); - installer.checkPendingCommands([]); + installer.checkPendingCommands(/*expectedCount*/ 0); host.reloadFS([f, fixedPackageJson]); host.triggerFileWatcherCallback(fixedPackageJson.path, /*removed*/ false); - // expected one view and one install request - installer.installAll([TI.NpmViewRequest], [TI.NpmInstallRequest]); + // expected install request + installer.installAll(/*expectedCount*/ 1); service.checkNumberOfProjects({ inferredProjects: 1 }); checkProjectActualFiles(service.inferredProjects[0], [f.path, commander.path]); @@ -783,12 +760,12 @@ namespace ts.projectSystem { const host = createServerHost([file]); const installer = new (class extends Installer { constructor() { - super(host, { globalTypingsCacheLocation: cachePath }); + super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("node", "commander") }); } - executeRequest(requestKind: TI.RequestKind, _requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) { + installWorker(_requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) { const installedTypings = ["@types/node", "@types/commander"]; const typingFiles = [node, commander]; - executeCommand(this, host, installedTypings, typingFiles, requestKind, cb); + executeCommand(this, host, installedTypings, typingFiles, cb); } })(); const service = createProjectService(host, { typingsInstaller: installer }); @@ -797,7 +774,7 @@ namespace ts.projectSystem { service.checkNumberOfProjects({ inferredProjects: 1 }); checkProjectActualFiles(service.inferredProjects[0], [file.path]); - installer.installAll([TI.NpmViewRequest, TI.NpmViewRequest], [TI.NpmInstallRequest]); + installer.installAll(/*expectedCount*/1); assert.isTrue(host.fileExists(node.path), "typings for 'node' should be created"); assert.isTrue(host.fileExists(commander.path), "typings for 'commander' should be created"); @@ -822,14 +799,10 @@ namespace ts.projectSystem { const host = createServerHost([f1]); const installer = new (class extends Installer { constructor() { - super(host, { globalTypingsCacheLocation: "/tmp" }); + super(host, { globalTypingsCacheLocation: "/tmp", typesRegistry: createTypesRegistry("foo") }); } - executeRequest(requestKind: TI.RequestKind, _requestId: number, args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) { - if (requestKind === TI.NpmViewRequest) { - // args should have only non-scoped packages - scoped packages are not yet supported - assert.deepEqual(args, ["foo"]); - } - executeCommand(this, host, ["foo"], [], requestKind, cb); + installWorker(_requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) { + executeCommand(this, host, ["foo"], [], cb); } })(); const projectService = createProjectService(host, { typingsInstaller: installer }); @@ -844,7 +817,7 @@ namespace ts.projectSystem { ["foo", "foo", "foo", "@bar/router", "@bar/common", "@bar/common"] ); - installer.installAll([TI.NpmViewRequest], [TI.NpmInstallRequest]); + installer.installAll(/*expectedCount*/ 1); }); it("cached unresolved typings are not recomputed if program structure did not change", () => { @@ -934,16 +907,16 @@ namespace ts.projectSystem { const host = createServerHost([f1, packageJson]); const installer = new (class extends Installer { constructor() { - super(host, { globalTypingsCacheLocation: "/tmp" }, { isEnabled: () => true, writeLine: msg => messages.push(msg) }); + super(host, { globalTypingsCacheLocation: "/tmp" }, /*telemetryEnabled*/ false, { isEnabled: () => true, writeLine: msg => messages.push(msg) }); } - executeRequest() { + installWorker(_requestId: number, _args: string[], _cwd: string, _cb: server.typingsInstaller.RequestCompletedAction) { assert(false, "runCommand should not be invoked"); } })(); const projectService = createProjectService(host, { typingsInstaller: installer }); projectService.openClientFile(f1.path); - installer.checkPendingCommands([]); + installer.checkPendingCommands(/*expectedCount*/ 0); assert.isTrue(messages.indexOf("Package name '; say ‘Hello from TypeScript!’ #' contains non URI safe characters") > 0, "should find package with invalid name"); }); }); @@ -978,4 +951,50 @@ namespace ts.projectSystem { assert.deepEqual(result.newTypingNames, ["bar"]); }); }); + + describe("telemetry events", () => { + it ("should be received", () => { + const f1 = { + path: "/a/app.js", + content: "" + }; + const package = { + path: "/a/package.json", + content: JSON.stringify({ dependencies: { "commander": "1.0.0" } }) + }; + const cachePath = "/a/cache/"; + const commander = { + path: cachePath + "node_modules/@types/commander/index.d.ts", + content: "export let x: number" + }; + const host = createServerHost([f1, package]); + let seenTelemetryEvent = false; + const installer = new (class extends Installer { + constructor() { + super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") }, /*telemetryEnabled*/ true); + } + installWorker(_requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) { + const installedTypings = ["@types/commander"]; + const typingFiles = [commander]; + executeCommand(this, host, installedTypings, typingFiles, cb); + } + sendResponse(response: server.SetTypings | server.InvalidateCachedTypings | server.TypingsInstallEvent) { + if (response.kind === server.EventInstall) { + assert.deepEqual(response.packagesToInstall, ["@types/commander"]); + seenTelemetryEvent = true; + return; + } + super.sendResponse(response); + } + })(); + const projectService = createProjectService(host, { typingsInstaller: installer }); + projectService.openClientFile(f1.path); + + installer.installAll(/*expectedCount*/ 1); + + assert.isTrue(seenTelemetryEvent); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]); + }); + }); } \ No newline at end of file diff --git a/src/lib/dom.generated.d.ts b/src/lib/dom.generated.d.ts index 9a87020d4f0..fd77f369fd8 100644 --- a/src/lib/dom.generated.d.ts +++ b/src/lib/dom.generated.d.ts @@ -7586,7 +7586,7 @@ declare var IDBCursorWithValue: { interface IDBDatabase extends EventTarget { readonly name: string; - readonly objectStoreNames: string[]; + readonly objectStoreNames: DOMStringList; onabort: (this: this, ev: Event) => any; onerror: (this: this, ev: ErrorEvent) => any; version: number; @@ -7652,7 +7652,7 @@ declare var IDBKeyRange: { } interface IDBObjectStore { - readonly indexNames: string[]; + readonly indexNames: DOMStringList; keyPath: string | string[]; readonly name: string; readonly transaction: IDBTransaction; diff --git a/src/lib/webworker.generated.d.ts b/src/lib/webworker.generated.d.ts index 4aeba696306..130510334d6 100644 --- a/src/lib/webworker.generated.d.ts +++ b/src/lib/webworker.generated.d.ts @@ -341,7 +341,7 @@ declare var IDBCursorWithValue: { interface IDBDatabase extends EventTarget { readonly name: string; - readonly objectStoreNames: string[]; + readonly objectStoreNames: DOMStringList; onabort: (this: this, ev: Event) => any; onerror: (this: this, ev: ErrorEvent) => any; version: number; @@ -407,7 +407,7 @@ declare var IDBKeyRange: { } interface IDBObjectStore { - readonly indexNames: string[]; + readonly indexNames: DOMStringList; keyPath: string | string[]; readonly name: string; readonly transaction: IDBTransaction; diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index a02becd87c4..269eea06464 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -286,10 +286,10 @@ namespace ts.server { return; } switch (response.kind) { - case "set": + case ActionSet: this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typingOptions, response.unresolvedImports, response.typings); break; - case "invalidate": + case ActionInvalidate: this.typingsCache.deleteTypingsForProject(response.projectName); break; } @@ -1288,7 +1288,9 @@ namespace ts.server { for (const file of proj.rootFiles) { const normalized = toNormalizedPath(file.fileName); if (getBaseFileName(normalized) === "tsconfig.json") { - (tsConfigFiles || (tsConfigFiles = [])).push(normalized); + if (this.host.fileExists(normalized)) { + (tsConfigFiles || (tsConfigFiles = [])).push(normalized); + } } else { rootFiles.push(file); diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index 628de71bb7a..f1e80d95880 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -13,6 +13,7 @@ namespace ts.server { private readonly resolveModuleName: typeof resolveModuleName; readonly trace: (s: string) => void; + readonly realpath?: (path: string) => string; constructor(private readonly host: ServerHost, private readonly project: Project, private readonly cancellationToken: HostCancellationToken) { this.getCanonicalFileName = ts.createGetCanonicalFileName(this.host.useCaseSensitiveFileNames); @@ -39,6 +40,10 @@ namespace ts.server { } return primaryResult; }; + + if (this.host.realpath) { + this.realpath = path => this.host.realpath(path); + } } public startRecordingFilesWithChangedResolutions() { diff --git a/src/server/project.ts b/src/server/project.ts index 563b0456910..d01df728248 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -301,7 +301,7 @@ namespace ts.server { return this.getLanguageService().getEmitOutput(info.fileName, emitOnlyDtsFiles); } - getFileNames() { + getFileNames(excludeFilesFromExternalLibraries?: boolean) { if (!this.program) { return []; } @@ -317,8 +317,14 @@ namespace ts.server { } return rootFiles; } - const sourceFiles = this.program.getSourceFiles(); - return sourceFiles.map(sourceFile => asNormalizedPath(sourceFile.fileName)); + const result: NormalizedPath[] = []; + for (const f of this.program.getSourceFiles()) { + if (excludeFilesFromExternalLibraries && this.program.isSourceFileFromExternalLibrary(f)) { + continue; + } + result.push(asNormalizedPath(f.fileName)); + } + return result; } getAllEmittableFiles() { diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 7da95e49575..d13caf7f01b 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -2057,6 +2057,32 @@ namespace ts.server.protocol { childItems?: NavigationTree[]; } + export type TelemetryEventName = "telemetry"; + + export interface TelemetryEvent extends Event { + event: TelemetryEventName; + body: TelemetryEventBody; + } + + export interface TelemetryEventBody { + telemetryEventName: string; + payload: any; + } + + export type TypingsInstalledTelemetryEventName = "typingsInstalled"; + + export interface TypingsInstalledTelemetryEventBody extends TelemetryEventBody { + telemetryEventName: TypingsInstalledTelemetryEventName; + payload: TypingsInstalledTelemetryEventPayload; + } + + export interface TypingsInstalledTelemetryEventPayload { + /** + * Comma separated list of installed typing packages + */ + installedPackages: string; + } + export interface NavBarResponse extends Response { body?: NavigationBarItem[]; } diff --git a/src/server/server.ts b/src/server/server.ts index 5057a13dbd1..3b33554aaba 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -1,4 +1,5 @@ /// +/// /// // used in fs.writeSync /* tslint:disable:no-null-keyword */ @@ -196,8 +197,10 @@ namespace ts.server { private socket: NodeSocket; private projectService: ProjectService; private throttledOperations: ThrottledOperations; + private telemetrySender: EventSender; constructor( + private readonly telemetryEnabled: boolean, private readonly logger: server.Logger, host: ServerHost, eventPort: number, @@ -226,15 +229,22 @@ namespace ts.server { this.socket.write(formatMessage({ seq, type: "event", event, body }, this.logger, Buffer.byteLength, this.newLine), "utf8"); } + setTelemetrySender(telemetrySender: EventSender) { + this.telemetrySender = telemetrySender; + } + attach(projectService: ProjectService) { this.projectService = projectService; if (this.logger.hasLevel(LogLevel.requestTime)) { this.logger.info("Binding..."); } - const args: string[] = ["--globalTypingsCacheLocation", this.globalTypingsCacheLocation]; + const args: string[] = [Arguments.GlobalCacheLocation, this.globalTypingsCacheLocation]; + if (this.telemetryEnabled) { + args.push(Arguments.EnableTelemetry); + } if (this.logger.loggingEnabled() && this.logger.getLogFileName()) { - args.push("--logFile", combinePaths(getDirectoryPath(normalizeSlashes(this.logger.getLogFileName())), `ti-${process.pid}.log`)); + args.push(Arguments.LogFile, combinePaths(getDirectoryPath(normalizeSlashes(this.logger.getLogFileName())), `ti-${process.pid}.log`)); } const execArgv: string[] = []; { @@ -280,12 +290,25 @@ namespace ts.server { }); } - private handleMessage(response: SetTypings | InvalidateCachedTypings) { + private handleMessage(response: SetTypings | InvalidateCachedTypings | TypingsInstallEvent) { if (this.logger.hasLevel(LogLevel.verbose)) { this.logger.info(`Received response: ${JSON.stringify(response)}`); } + if (response.kind === EventInstall) { + if (this.telemetrySender) { + const body: protocol.TypingsInstalledTelemetryEventBody = { + telemetryEventName: "typingsInstalled", + payload: { + installedPackages: response.packagesToInstall.join(",") + } + }; + const eventName: protocol.TelemetryEventName = "telemetry"; + this.telemetrySender.event(body, eventName); + } + return; + } this.projectService.updateTypingsForProject(response); - if (response.kind == "set" && this.socket) { + if (response.kind == ActionSet && this.socket) { this.sendEvent(0, "setTypings", response); } } @@ -300,18 +323,25 @@ namespace ts.server { useSingleInferredProject: boolean, disableAutomaticTypingAcquisition: boolean, globalTypingsCacheLocation: string, + telemetryEnabled: boolean, logger: server.Logger) { - super( - host, - cancellationToken, - useSingleInferredProject, - disableAutomaticTypingAcquisition - ? nullTypingsInstaller - : new NodeTypingsInstaller(logger, host, installerEventPort, globalTypingsCacheLocation, host.newLine), - Buffer.byteLength, - process.hrtime, - logger, - canUseEvents); + const typingsInstaller = disableAutomaticTypingAcquisition + ? undefined + : new NodeTypingsInstaller(telemetryEnabled, logger, host, installerEventPort, globalTypingsCacheLocation, host.newLine); + + super( + host, + cancellationToken, + useSingleInferredProject, + typingsInstaller || nullTypingsInstaller, + Buffer.byteLength, + process.hrtime, + logger, + canUseEvents); + + if (telemetryEnabled && typingsInstaller) { + typingsInstaller.setTelemetrySender(this); + } } exit() { @@ -538,17 +568,17 @@ namespace ts.server { let eventPort: number; { - const index = sys.args.indexOf("--eventPort"); - if (index >= 0 && index < sys.args.length - 1) { - const v = parseInt(sys.args[index + 1]); - if (!isNaN(v)) { - eventPort = v; - } + const str = findArgument("--eventPort"); + const v = str && parseInt(str); + if (!isNaN(v)) { + eventPort = v; } } - const useSingleInferredProject = sys.args.indexOf("--useSingleInferredProject") >= 0; - const disableAutomaticTypingAcquisition = sys.args.indexOf("--disableAutomaticTypingAcquisition") >= 0; + const useSingleInferredProject = hasArgument("--useSingleInferredProject"); + const disableAutomaticTypingAcquisition = hasArgument("--disableAutomaticTypingAcquisition"); + const telemetryEnabled = hasArgument(Arguments.EnableTelemetry); + const ioSession = new IOSession( sys, cancellationToken, @@ -557,6 +587,7 @@ namespace ts.server { useSingleInferredProject, disableAutomaticTypingAcquisition, getGlobalTypingsCacheLocation(), + telemetryEnabled, logger); process.on("uncaughtException", function (err: Error) { ioSession.logError(err, "unknown"); diff --git a/src/server/session.ts b/src/server/session.ts index 545701f1449..b250393b7ff 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -73,6 +73,10 @@ namespace ts.server { project: Project; } + export interface EventSender { + event(payload: any, eventName: string): void; + } + function allEditsBeforePos(edits: ts.TextChange[], pos: number) { for (const edit of edits) { if (textSpanEnd(edit.span) >= pos) { @@ -165,7 +169,7 @@ namespace ts.server { return `Content-Length: ${1 + len}\r\n\r\n${json}${newLine}`; } - export class Session { + export class Session implements EventSender { private readonly gcTimer: GcTimer; protected projectService: ProjectService; private errorTimer: any; /*NodeJS.Timer | number*/ diff --git a/src/server/shared.ts b/src/server/shared.ts new file mode 100644 index 00000000000..81a1f7fb55b --- /dev/null +++ b/src/server/shared.ts @@ -0,0 +1,24 @@ +/// + +namespace ts.server { + export const ActionSet: ActionSet = "action::set"; + export const ActionInvalidate: ActionInvalidate = "action::invalidate"; + export const EventInstall: EventInstall = "event::install"; + + export namespace Arguments { + export const GlobalCacheLocation = "--globalTypingsCacheLocation"; + export const LogFile = "--logFile"; + export const EnableTelemetry = "--enableTelemetry"; + } + + export function hasArgument(argumentName: string) { + return sys.args.indexOf(argumentName) >= 0; + } + + export function findArgument(argumentName: string) { + const index = sys.args.indexOf(argumentName); + return index >= 0 && index < sys.args.length - 1 + ? sys.args[index + 1] + : undefined; + } +} \ No newline at end of file diff --git a/src/server/tsconfig.json b/src/server/tsconfig.json index a99994d97c5..85c88679164 100644 --- a/src/server/tsconfig.json +++ b/src/server/tsconfig.json @@ -18,6 +18,7 @@ "files": [ "../services/shims.ts", "../services/utilities.ts", + "shared.ts", "utilities.ts", "scriptVersionCache.ts", "scriptInfo.ts", diff --git a/src/server/tsconfig.library.json b/src/server/tsconfig.library.json index e269629b558..5483cc8ec28 100644 --- a/src/server/tsconfig.library.json +++ b/src/server/tsconfig.library.json @@ -15,6 +15,7 @@ "files": [ "../services/shims.ts", "../services/utilities.ts", + "shared.ts", "utilities.ts", "scriptVersionCache.ts", "scriptInfo.ts", diff --git a/src/server/types.d.ts b/src/server/types.d.ts index ec2befe8fa9..aebc3121252 100644 --- a/src/server/types.d.ts +++ b/src/server/types.d.ts @@ -41,28 +41,38 @@ declare namespace ts.server { readonly kind: "closeProject"; } - export type SetRequest = "set"; - export type InvalidateRequest = "invalidate"; + export type ActionSet = "action::set"; + export type ActionInvalidate = "action::invalidate"; + export type EventInstall = "event::install"; + export interface TypingInstallerResponse { - readonly projectName: string; - readonly kind: SetRequest | InvalidateRequest; + readonly kind: ActionSet | ActionInvalidate | EventInstall; } - export interface SetTypings extends TypingInstallerResponse { + export interface ProjectResponse extends TypingInstallerResponse { + readonly projectName: string; + } + + export interface SetTypings extends ProjectResponse { readonly typingOptions: ts.TypingOptions; readonly compilerOptions: ts.CompilerOptions; readonly typings: string[]; readonly unresolvedImports: SortedReadonlyArray; - readonly kind: SetRequest; + readonly kind: ActionSet; } - export interface InvalidateCachedTypings extends TypingInstallerResponse { - readonly kind: InvalidateRequest; + export interface InvalidateCachedTypings extends ProjectResponse { + readonly kind: ActionInvalidate; + } + + export interface TypingsInstallEvent extends TypingInstallerResponse { + readonly packagesToInstall: ReadonlyArray; + readonly kind: EventInstall; } export interface InstallTypingHost extends JsTyping.TypingResolutionHost { writeFile(path: string, content: string): void; createDirectory(path: string): void; - watchFile?(path: string, callback: FileWatcherCallback): FileWatcher; + watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher; } } \ No newline at end of file diff --git a/src/server/typingsInstaller/nodeTypingsInstaller.ts b/src/server/typingsInstaller/nodeTypingsInstaller.ts index 89419264930..7020b6aa4f8 100644 --- a/src/server/typingsInstaller/nodeTypingsInstaller.ts +++ b/src/server/typingsInstaller/nodeTypingsInstaller.ts @@ -33,42 +33,81 @@ namespace ts.server.typingsInstaller { } } - type HttpGet = { - (url: string, callback: (response: HttpResponse) => void): NodeJS.EventEmitter; - }; - - interface HttpResponse extends NodeJS.ReadableStream { - statusCode: number; - statusMessage: string; - destroy(): void; + interface TypesRegistryFile { + entries: MapLike; } + function loadTypesRegistryFile(typesRegistryFilePath: string, host: InstallTypingHost, log: Log): Map { + if (!host.fileExists(typesRegistryFilePath)) { + if (log.isEnabled()) { + log.writeLine(`Types registry file '${typesRegistryFilePath}' does not exist`); + } + return createMap(); + } + try { + const content = JSON.parse(host.readFile(typesRegistryFilePath)); + return createMap(content.entries); + } + catch (e) { + if (log.isEnabled()) { + log.writeLine(`Error when loading types registry file '${typesRegistryFilePath}': ${(e).message}, ${(e).stack}`); + } + return createMap(); + } + } + + const TypesRegistryPackageName = "types-registry"; + function getTypesRegistryFileLocation(globalTypingsCacheLocation: string): string { + return combinePaths(normalizeSlashes(globalTypingsCacheLocation), `node_modules/${TypesRegistryPackageName}/index.json`); + } + + type Exec = { (command: string, options: { cwd: string }, callback?: (error: Error, stdout: string, stderr: string) => void): any }; + type ExecSync = { + (command: string, options: { cwd: string, stdio: "ignore" }): any + }; + export class NodeTypingsInstaller extends TypingsInstaller { private readonly exec: Exec; - private readonly httpGet: HttpGet; private readonly npmPath: string; - readonly installTypingHost: InstallTypingHost = sys; + readonly typesRegistry: Map; - constructor(globalTypingsCacheLocation: string, throttleLimit: number, log: Log) { + constructor(globalTypingsCacheLocation: string, throttleLimit: number, telemetryEnabled: boolean, log: Log) { super( + sys, globalTypingsCacheLocation, toPath("typingSafeList.json", __dirname, createGetCanonicalFileName(sys.useCaseSensitiveFileNames)), throttleLimit, + telemetryEnabled, log); if (this.log.isEnabled()) { this.log.writeLine(`Process id: ${process.pid}`); } this.npmPath = getNPMLocation(process.argv[0]); - this.exec = require("child_process").exec; - this.httpGet = require("http").get; + let execSync: ExecSync; + ({ exec: this.exec, execSync } = require("child_process")); + + this.ensurePackageDirectoryExists(globalTypingsCacheLocation); + + try { + if (this.log.isEnabled()) { + this.log.writeLine(`Updating ${TypesRegistryPackageName} npm package...`); + } + execSync(`${this.npmPath} install ${TypesRegistryPackageName}`, { cwd: globalTypingsCacheLocation, stdio: "ignore" }); + } + catch (e) { + if (this.log.isEnabled()) { + this.log.writeLine(`Error updating ${TypesRegistryPackageName} package: ${(e).message}`); + } + } + + this.typesRegistry = loadTypesRegistryFile(getTypesRegistryFileLocation(globalTypingsCacheLocation), this.installTypingHost, this.log); } - init() { - super.init(); + listen() { process.on("message", (req: DiscoverTypings | CloseProject) => { switch (req.kind) { case "discover": @@ -90,66 +129,26 @@ namespace ts.server.typingsInstaller { } } - protected executeRequest(requestKind: RequestKind, requestId: number, args: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void { + protected installWorker(requestId: number, args: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void { if (this.log.isEnabled()) { - this.log.writeLine(`#${requestId} executing ${requestKind}, arguments'${JSON.stringify(args)}'.`); - } - switch (requestKind) { - case NpmViewRequest: { - // const command = `${self.npmPath} view @types/${typing} --silent name`; - // use http request to global npm registry instead of running npm view - Debug.assert(args.length === 1); - const url = `http://registry.npmjs.org/@types%2f${args[0]}`; - const start = Date.now(); - this.httpGet(url, response => { - let ok = false; - if (this.log.isEnabled()) { - this.log.writeLine(`${requestKind} #${requestId} request to ${url}:: status code ${response.statusCode}, status message '${response.statusMessage}', took ${Date.now() - start} ms`); - } - switch (response.statusCode) { - case 200: // OK - case 301: // redirect - Moved - treat package as present - case 302: // redirect - Found - treat package as present - ok = true; - break; - } - response.destroy(); - onRequestCompleted(ok); - }).on("error", (err: Error) => { - if (this.log.isEnabled()) { - this.log.writeLine(`${requestKind} #${requestId} query to npm registry failed with error ${err.message}, stack ${err.stack}`); - } - onRequestCompleted(/*success*/ false); - }); - } - break; - case NpmInstallRequest: { - const command = `${this.npmPath} install ${args.join(" ")} --save-dev`; - const start = Date.now(); - this.exec(command, { cwd }, (_err, stdout, stderr) => { - if (this.log.isEnabled()) { - this.log.writeLine(`${requestKind} #${requestId} took: ${Date.now() - start} ms${sys.newLine}stdout: ${stdout}${sys.newLine}stderr: ${stderr}`); - } - // treat any output on stdout as success - onRequestCompleted(!!stdout); - }); - } - break; - default: - Debug.assert(false, `Unknown request kind ${requestKind}`); + this.log.writeLine(`#${requestId} with arguments'${JSON.stringify(args)}'.`); } + const command = `${this.npmPath} install ${args.join(" ")} --save-dev`; + const start = Date.now(); + this.exec(command, { cwd }, (_err, stdout, stderr) => { + if (this.log.isEnabled()) { + this.log.writeLine(`npm install #${requestId} took: ${Date.now() - start} ms${sys.newLine}stdout: ${stdout}${sys.newLine}stderr: ${stderr}`); + } + // treat any output on stdout as success + onRequestCompleted(!!stdout); + }); } } - function findArgument(argumentName: string) { - const index = sys.args.indexOf(argumentName); - return index >= 0 && index < sys.args.length - 1 - ? sys.args[index + 1] - : undefined; - } + const logFilePath = findArgument(server.Arguments.LogFile); + const globalTypingsCacheLocation = findArgument(server.Arguments.GlobalCacheLocation); + const telemetryEnabled = hasArgument(server.Arguments.EnableTelemetry); - const logFilePath = findArgument("--logFile"); - const globalTypingsCacheLocation = findArgument("--globalTypingsCacheLocation"); const log = new FileLog(logFilePath); if (log.isEnabled()) { process.on("uncaughtException", (e: Error) => { @@ -162,6 +161,6 @@ namespace ts.server.typingsInstaller { } process.exit(0); }); - const installer = new NodeTypingsInstaller(globalTypingsCacheLocation, /*throttleLimit*/5, log); - installer.init(); + const installer = new NodeTypingsInstaller(globalTypingsCacheLocation, /*throttleLimit*/5, telemetryEnabled, log); + installer.listen(); } \ No newline at end of file diff --git a/src/server/typingsInstaller/tsconfig.json b/src/server/typingsInstaller/tsconfig.json index c9b4d8f0ad1..c6031b19aae 100644 --- a/src/server/typingsInstaller/tsconfig.json +++ b/src/server/typingsInstaller/tsconfig.json @@ -17,6 +17,7 @@ }, "files": [ "../types.d.ts", + "../shared.ts", "typingsInstaller.ts", "nodeTypingsInstaller.ts" ] diff --git a/src/server/typingsInstaller/typingsInstaller.ts b/src/server/typingsInstaller/typingsInstaller.ts index df043fc26ae..da97dbcd194 100644 --- a/src/server/typingsInstaller/typingsInstaller.ts +++ b/src/server/typingsInstaller/typingsInstaller.ts @@ -2,6 +2,7 @@ /// /// /// +/// namespace ts.server.typingsInstaller { interface NpmConfig { @@ -63,14 +64,8 @@ namespace ts.server.typingsInstaller { return PackageNameValidationResult.Ok; } - export const NpmViewRequest: "npm view" = "npm view"; - export const NpmInstallRequest: "npm install" = "npm install"; - - export type RequestKind = typeof NpmViewRequest | typeof NpmInstallRequest; - export type RequestCompletedAction = (success: boolean) => void; type PendingRequest = { - requestKind: RequestKind; requestId: number; args: string[]; cwd: string; @@ -87,19 +82,18 @@ namespace ts.server.typingsInstaller { private installRunCount = 1; private inFlightRequestCount = 0; - abstract readonly installTypingHost: InstallTypingHost; + abstract readonly typesRegistry: Map; constructor( + readonly installTypingHost: InstallTypingHost, readonly globalCachePath: string, readonly safeListPath: Path, readonly throttleLimit: number, + readonly telemetryEnabled: boolean, protected readonly log = nullLog) { if (this.log.isEnabled()) { this.log.writeLine(`Global cache location '${globalCachePath}', safe file path '${safeListPath}'`); } - } - - init() { this.processCacheLocation(this.globalCachePath); } @@ -224,7 +218,7 @@ namespace ts.server.typingsInstaller { this.knownCachesSet[cacheLocation] = true; } - private filterTypings(typingsToInstall: string[]) { + private filterAndMapToScopedName(typingsToInstall: string[]) { if (typingsToInstall.length === 0) { return typingsToInstall; } @@ -235,7 +229,14 @@ namespace ts.server.typingsInstaller { } const validationResult = validatePackageName(typing); if (validationResult === PackageNameValidationResult.Ok) { - result.push(typing); + if (typing in this.typesRegistry) { + result.push(`@types/${typing}`); + } + else { + if (this.log.isEnabled()) { + this.log.writeLine(`Entry for package '${typing}' does not exist in local types registry - skipping...`); + } + } } else { // add typing name to missing set so we won't process it again @@ -267,19 +268,8 @@ namespace ts.server.typingsInstaller { return result; } - private installTypings(req: DiscoverTypings, cachePath: string, currentlyCachedTypings: string[], typingsToInstall: string[]) { - if (this.log.isEnabled()) { - this.log.writeLine(`Installing typings ${JSON.stringify(typingsToInstall)}`); - } - typingsToInstall = this.filterTypings(typingsToInstall); - if (typingsToInstall.length === 0) { - if (this.log.isEnabled()) { - this.log.writeLine(`All typings are known to be missing or invalid - no need to go any further`); - } - return; - } - - const npmConfigPath = combinePaths(cachePath, "package.json"); + protected ensurePackageDirectoryExists(directory: string) { + const npmConfigPath = combinePaths(directory, "package.json"); if (this.log.isEnabled()) { this.log.writeLine(`Npm config file: ${npmConfigPath}`); } @@ -287,23 +277,50 @@ namespace ts.server.typingsInstaller { if (this.log.isEnabled()) { this.log.writeLine(`Npm config file: '${npmConfigPath}' is missing, creating new one...`); } - this.ensureDirectoryExists(cachePath, this.installTypingHost); + this.ensureDirectoryExists(directory, this.installTypingHost); this.installTypingHost.writeFile(npmConfigPath, "{}"); } + } + + private installTypings(req: DiscoverTypings, cachePath: string, currentlyCachedTypings: string[], typingsToInstall: string[]) { + if (this.log.isEnabled()) { + this.log.writeLine(`Installing typings ${JSON.stringify(typingsToInstall)}`); + } + const scopedTypings = this.filterAndMapToScopedName(typingsToInstall); + if (scopedTypings.length === 0) { + if (this.log.isEnabled()) { + this.log.writeLine(`All typings are known to be missing or invalid - no need to go any further`); + } + return; + } + + this.ensurePackageDirectoryExists(cachePath); + + const requestId = this.installRunCount; + this.installRunCount++; + + this.installTypingsAsync(requestId, scopedTypings, cachePath, ok => { + if (this.telemetryEnabled) { + this.sendResponse({ + kind: EventInstall, + packagesToInstall: scopedTypings + }); + } + + if (!ok) { + return; + } - this.runInstall(cachePath, typingsToInstall, installedTypings => { // TODO: watch project directory if (this.log.isEnabled()) { - this.log.writeLine(`Requested to install typings ${JSON.stringify(typingsToInstall)}, installed typings ${JSON.stringify(installedTypings)}`); + this.log.writeLine(`Requested to install typings ${JSON.stringify(scopedTypings)}, installed typings ${JSON.stringify(scopedTypings)}`); } - const installedPackages: Map = createMap(); const installedTypingFiles: string[] = []; - for (const t of installedTypings) { + for (const t of scopedTypings) { const packageName = getBaseFileName(t); if (!packageName) { continue; } - installedPackages[packageName] = true; const typingFile = typingToFileName(cachePath, packageName, this.installTypingHost); if (!typingFile) { continue; @@ -316,53 +333,11 @@ namespace ts.server.typingsInstaller { if (this.log.isEnabled()) { this.log.writeLine(`Installed typing files ${JSON.stringify(installedTypingFiles)}`); } - for (const toInstall of typingsToInstall) { - if (!installedPackages[toInstall]) { - if (this.log.isEnabled()) { - this.log.writeLine(`New missing typing package '${toInstall}'`); - } - this.missingTypingsSet[toInstall] = true; - } - } this.sendResponse(this.createSetTypings(req, currentlyCachedTypings.concat(installedTypingFiles))); }); } - private runInstall(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void): void { - const requestId = this.installRunCount; - - this.installRunCount++; - let execInstallCmdCount = 0; - const filteredTypings: string[] = []; - for (const typing of typingsToInstall) { - filterExistingTypings(this, typing); - } - - function filterExistingTypings(self: TypingsInstaller, typing: string) { - self.execAsync(NpmViewRequest, requestId, [typing], cachePath, ok => { - if (ok) { - filteredTypings.push(typing); - } - execInstallCmdCount++; - if (execInstallCmdCount === typingsToInstall.length) { - installFilteredTypings(self, filteredTypings); - } - }); - } - - function installFilteredTypings(self: TypingsInstaller, filteredTypings: string[]) { - if (filteredTypings.length === 0) { - postInstallAction([]); - return; - } - const scopedTypings = filteredTypings.map(t => "@types/" + t); - self.execAsync(NpmInstallRequest, requestId, scopedTypings, cachePath, ok => { - postInstallAction(ok ? scopedTypings : []); - }); - } - } - private ensureDirectoryExists(directory: string, host: InstallTypingHost): void { const directoryName = getDirectoryPath(directory); if (!host.directoryExists(directoryName)) { @@ -389,10 +364,10 @@ namespace ts.server.typingsInstaller { this.log.writeLine(`Got FS notification for ${f}, handler is already invoked '${isInvoked}'`); } if (!isInvoked) { - this.sendResponse({ projectName: projectName, kind: "invalidate" }); + this.sendResponse({ projectName: projectName, kind: server.ActionInvalidate }); isInvoked = true; } - }); + }, /*pollingInterval*/ 2000); watchers.push(w); } this.projectWatchers[projectName] = watchers; @@ -405,12 +380,12 @@ namespace ts.server.typingsInstaller { compilerOptions: request.compilerOptions, typings, unresolvedImports: request.unresolvedImports, - kind: "set" + kind: server.ActionSet }; } - private execAsync(requestKind: RequestKind, requestId: number, args: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void { - this.pendingRunRequests.unshift({ requestKind, requestId, args, cwd, onRequestCompleted }); + private installTypingsAsync(requestId: number, args: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void { + this.pendingRunRequests.unshift({ requestId, args, cwd, onRequestCompleted }); this.executeWithThrottling(); } @@ -418,7 +393,7 @@ namespace ts.server.typingsInstaller { while (this.inFlightRequestCount < this.throttleLimit && this.pendingRunRequests.length) { this.inFlightRequestCount++; const request = this.pendingRunRequests.pop(); - this.executeRequest(request.requestKind, request.requestId, request.args, request.cwd, ok => { + this.installWorker(request.requestId, request.args, request.cwd, ok => { this.inFlightRequestCount--; request.onRequestCompleted(ok); this.executeWithThrottling(); @@ -426,7 +401,7 @@ namespace ts.server.typingsInstaller { } } - protected abstract executeRequest(requestKind: RequestKind, requestId: number, args: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void; - protected abstract sendResponse(response: SetTypings | InvalidateCachedTypings): void; + protected abstract installWorker(requestId: number, args: string[], cwd: string, onRequestCompleted: RequestCompletedAction): void; + protected abstract sendResponse(response: SetTypings | InvalidateCachedTypings | TypingsInstallEvent): void; } } \ No newline at end of file diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 8806b759e3f..ac809652119 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -1,4 +1,5 @@ /// +/// namespace ts.server { export enum LogLevel { @@ -10,6 +11,7 @@ namespace ts.server { export const emptyArray: ReadonlyArray = []; + export interface Logger { close(): void; hasLevel(level: LogLevel): boolean; @@ -48,7 +50,7 @@ namespace ts.server { export function createInstallTypingsRequest(project: Project, typingOptions: TypingOptions, unresolvedImports: SortedReadonlyArray, cachePath?: string): DiscoverTypings { return { projectName: project.getProjectName(), - fileNames: project.getFileNames(), + fileNames: project.getFileNames(/*excludeFilesFromExternalLibraries*/ true), compilerOptions: project.getCompilerOptions(), typingOptions, unresolvedImports, diff --git a/src/services/jsTyping.ts b/src/services/jsTyping.ts index 0f635c15174..316dcd355c2 100644 --- a/src/services/jsTyping.ts +++ b/src/services/jsTyping.ts @@ -88,7 +88,7 @@ namespace ts.JsTyping { exclude = typingOptions.exclude || []; const possibleSearchDirs = map(fileNames, getDirectoryPath); - if (projectRootPath !== undefined) { + if (projectRootPath) { possibleSearchDirs.push(projectRootPath); } searchDirs = deduplicate(possibleSearchDirs); diff --git a/src/services/services.ts b/src/services/services.ts index 8abcf8f7684..56e604abeb3 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -48,7 +48,6 @@ namespace ts { public jsDocComments: JSDoc[]; public original: Node; public transformFlags: TransformFlags; - public excludeTransformFlags: TransformFlags; private _children: Node[]; constructor(kind: SyntaxKind, pos: number, end: number) { @@ -56,7 +55,6 @@ namespace ts { this.end = end; this.flags = NodeFlags.None; this.transformFlags = undefined; - this.excludeTransformFlags = undefined; this.parent = undefined; this.kind = kind; } diff --git a/tests/baselines/reference/declarationEmitTypeAliasWithTypeParameters1.js b/tests/baselines/reference/declarationEmitTypeAliasWithTypeParameters1.js index c41c4690391..6079de35964 100644 --- a/tests/baselines/reference/declarationEmitTypeAliasWithTypeParameters1.js +++ b/tests/baselines/reference/declarationEmitTypeAliasWithTypeParameters1.js @@ -12,4 +12,4 @@ exports.y = function (x) { return 1; }; //// [declarationEmitTypeAliasWithTypeParameters1.d.ts] export declare type Bar = () => [X, Y]; export declare type Foo = Bar; -export declare const y: (x: () => [any, string]) => number; +export declare const y: (x: Bar) => number; diff --git a/tests/baselines/reference/declarationEmitTypeAliasWithTypeParameters1.types b/tests/baselines/reference/declarationEmitTypeAliasWithTypeParameters1.types index 3ff9f9bde1d..a8b0f146f8e 100644 --- a/tests/baselines/reference/declarationEmitTypeAliasWithTypeParameters1.types +++ b/tests/baselines/reference/declarationEmitTypeAliasWithTypeParameters1.types @@ -8,15 +8,15 @@ export type Bar = () => [X, Y]; >Y : Y export type Foo = Bar; ->Foo : () => [any, Y] +>Foo : Bar >Y : Y >Bar : Bar >Y : Y export const y = (x: Foo) => 1 ->y : (x: () => [any, string]) => number ->(x: Foo) => 1 : (x: () => [any, string]) => number ->x : () => [any, string] ->Foo : () => [any, Y] +>y : (x: Bar) => number +>(x: Foo) => 1 : (x: Bar) => number +>x : Bar +>Foo : Bar >1 : 1 diff --git a/tests/baselines/reference/instantiatedTypeAliasDisplay.js b/tests/baselines/reference/instantiatedTypeAliasDisplay.js new file mode 100644 index 00000000000..9c2b91b613e --- /dev/null +++ b/tests/baselines/reference/instantiatedTypeAliasDisplay.js @@ -0,0 +1,36 @@ +//// [instantiatedTypeAliasDisplay.ts] + +// Repros from #12066 + +interface X { + a: A; +} +interface Y { + b: B; +} +type Z = X | Y; + +declare function f1(): Z; +declare function f2(a: A, b: B, c: C, d: D): Z; + +const x1 = f1(); // Z +const x2 = f2({}, {}, {}, {}); // Z<{}, string[]> + +//// [instantiatedTypeAliasDisplay.js] +// Repros from #12066 +var x1 = f1(); // Z +var x2 = f2({}, {}, {}, {}); // Z<{}, string[]> + + +//// [instantiatedTypeAliasDisplay.d.ts] +interface X { + a: A; +} +interface Y { + b: B; +} +declare type Z = X | Y; +declare function f1(): Z; +declare function f2(a: A, b: B, c: C, d: D): Z; +declare const x1: Z; +declare const x2: Z<{}, string[]>; diff --git a/tests/baselines/reference/instantiatedTypeAliasDisplay.symbols b/tests/baselines/reference/instantiatedTypeAliasDisplay.symbols new file mode 100644 index 00000000000..fa8140cfcd7 --- /dev/null +++ b/tests/baselines/reference/instantiatedTypeAliasDisplay.symbols @@ -0,0 +1,61 @@ +=== tests/cases/compiler/instantiatedTypeAliasDisplay.ts === + +// Repros from #12066 + +interface X { +>X : Symbol(X, Decl(instantiatedTypeAliasDisplay.ts, 0, 0)) +>A : Symbol(A, Decl(instantiatedTypeAliasDisplay.ts, 3, 12)) + + a: A; +>a : Symbol(X.a, Decl(instantiatedTypeAliasDisplay.ts, 3, 16)) +>A : Symbol(A, Decl(instantiatedTypeAliasDisplay.ts, 3, 12)) +} +interface Y { +>Y : Symbol(Y, Decl(instantiatedTypeAliasDisplay.ts, 5, 1)) +>B : Symbol(B, Decl(instantiatedTypeAliasDisplay.ts, 6, 12)) + + b: B; +>b : Symbol(Y.b, Decl(instantiatedTypeAliasDisplay.ts, 6, 16)) +>B : Symbol(B, Decl(instantiatedTypeAliasDisplay.ts, 6, 12)) +} +type Z = X | Y; +>Z : Symbol(Z, Decl(instantiatedTypeAliasDisplay.ts, 8, 1)) +>A : Symbol(A, Decl(instantiatedTypeAliasDisplay.ts, 9, 7)) +>B : Symbol(B, Decl(instantiatedTypeAliasDisplay.ts, 9, 9)) +>X : Symbol(X, Decl(instantiatedTypeAliasDisplay.ts, 0, 0)) +>A : Symbol(A, Decl(instantiatedTypeAliasDisplay.ts, 9, 7)) +>Y : Symbol(Y, Decl(instantiatedTypeAliasDisplay.ts, 5, 1)) +>B : Symbol(B, Decl(instantiatedTypeAliasDisplay.ts, 9, 9)) + +declare function f1(): Z; +>f1 : Symbol(f1, Decl(instantiatedTypeAliasDisplay.ts, 9, 27)) +>A : Symbol(A, Decl(instantiatedTypeAliasDisplay.ts, 11, 20)) +>Z : Symbol(Z, Decl(instantiatedTypeAliasDisplay.ts, 8, 1)) +>A : Symbol(A, Decl(instantiatedTypeAliasDisplay.ts, 11, 20)) + +declare function f2(a: A, b: B, c: C, d: D): Z; +>f2 : Symbol(f2, Decl(instantiatedTypeAliasDisplay.ts, 11, 39)) +>A : Symbol(A, Decl(instantiatedTypeAliasDisplay.ts, 12, 20)) +>B : Symbol(B, Decl(instantiatedTypeAliasDisplay.ts, 12, 22)) +>C : Symbol(C, Decl(instantiatedTypeAliasDisplay.ts, 12, 25)) +>D : Symbol(D, Decl(instantiatedTypeAliasDisplay.ts, 12, 28)) +>E : Symbol(E, Decl(instantiatedTypeAliasDisplay.ts, 12, 31)) +>a : Symbol(a, Decl(instantiatedTypeAliasDisplay.ts, 12, 35)) +>A : Symbol(A, Decl(instantiatedTypeAliasDisplay.ts, 12, 20)) +>b : Symbol(b, Decl(instantiatedTypeAliasDisplay.ts, 12, 40)) +>B : Symbol(B, Decl(instantiatedTypeAliasDisplay.ts, 12, 22)) +>c : Symbol(c, Decl(instantiatedTypeAliasDisplay.ts, 12, 46)) +>C : Symbol(C, Decl(instantiatedTypeAliasDisplay.ts, 12, 25)) +>d : Symbol(d, Decl(instantiatedTypeAliasDisplay.ts, 12, 52)) +>D : Symbol(D, Decl(instantiatedTypeAliasDisplay.ts, 12, 28)) +>Z : Symbol(Z, Decl(instantiatedTypeAliasDisplay.ts, 8, 1)) +>A : Symbol(A, Decl(instantiatedTypeAliasDisplay.ts, 12, 20)) + +const x1 = f1(); // Z +>x1 : Symbol(x1, Decl(instantiatedTypeAliasDisplay.ts, 14, 5)) +>f1 : Symbol(f1, Decl(instantiatedTypeAliasDisplay.ts, 9, 27)) + +const x2 = f2({}, {}, {}, {}); // Z<{}, string[]> +>x2 : Symbol(x2, Decl(instantiatedTypeAliasDisplay.ts, 15, 5)) +>f2 : Symbol(f2, Decl(instantiatedTypeAliasDisplay.ts, 11, 39)) + diff --git a/tests/baselines/reference/instantiatedTypeAliasDisplay.types b/tests/baselines/reference/instantiatedTypeAliasDisplay.types new file mode 100644 index 00000000000..89320e1dba6 --- /dev/null +++ b/tests/baselines/reference/instantiatedTypeAliasDisplay.types @@ -0,0 +1,67 @@ +=== tests/cases/compiler/instantiatedTypeAliasDisplay.ts === + +// Repros from #12066 + +interface X { +>X : X +>A : A + + a: A; +>a : A +>A : A +} +interface Y { +>Y : Y +>B : B + + b: B; +>b : B +>B : B +} +type Z = X | Y; +>Z : Z +>A : A +>B : B +>X : X +>A : A +>Y : Y +>B : B + +declare function f1(): Z; +>f1 : () => Z +>A : A +>Z : Z +>A : A + +declare function f2(a: A, b: B, c: C, d: D): Z; +>f2 : (a: A, b: B, c: C, d: D) => Z +>A : A +>B : B +>C : C +>D : D +>E : E +>a : A +>A : A +>b : B +>B : B +>c : C +>C : C +>d : D +>D : D +>Z : Z +>A : A + +const x1 = f1(); // Z +>x1 : Z +>f1() : Z +>f1 : () => Z + +const x2 = f2({}, {}, {}, {}); // Z<{}, string[]> +>x2 : Z<{}, string[]> +>f2({}, {}, {}, {}) : Z<{}, string[]> +>f2 : (a: A, b: B, c: C, d: D) => Z +>{} : {} +>{} : {} +>{} : {} +>{} : {} + diff --git a/tests/baselines/reference/promisePermutations.errors.txt b/tests/baselines/reference/promisePermutations.errors.txt index 65fa0981643..13fc0718596 100644 --- a/tests/baselines/reference/promisePermutations.errors.txt +++ b/tests/baselines/reference/promisePermutations.errors.txt @@ -25,8 +25,6 @@ tests/cases/compiler/promisePermutations.ts(106,19): error TS2345: Argument of t Types of parameters 'cb' and 'value' are incompatible. Type 'string' is not assignable to type '(a: T) => T'. tests/cases/compiler/promisePermutations.ts(109,19): error TS2345: Argument of type '(cb: (a: T) => T) => IPromise' is not assignable to parameter of type '(value: string) => IPromise'. - Types of parameters 'cb' and 'value' are incompatible. - Type 'string' is not assignable to type '(a: T) => T'. tests/cases/compiler/promisePermutations.ts(110,19): error TS2345: Argument of type '(cb: (a: T) => T) => Promise' is not assignable to parameter of type '(value: string) => Promise'. Types of parameters 'cb' and 'value' are incompatible. Type 'string' is not assignable to type '(a: T) => T'. @@ -229,8 +227,6 @@ tests/cases/compiler/promisePermutations.ts(160,21): error TS2345: Argument of t var s7a = r7.then(testFunction7, testFunction7, testFunction7); // error ~~~~~~~~~~~~~ !!! error TS2345: Argument of type '(cb: (a: T) => T) => IPromise' is not assignable to parameter of type '(value: string) => IPromise'. -!!! error TS2345: Types of parameters 'cb' and 'value' are incompatible. -!!! error TS2345: Type 'string' is not assignable to type '(a: T) => T'. var s7b = r7.then(testFunction7P, testFunction7P, testFunction7P); // error ~~~~~~~~~~~~~~ !!! error TS2345: Argument of type '(cb: (a: T) => T) => Promise' is not assignable to parameter of type '(value: string) => Promise'. diff --git a/tests/baselines/reference/promisePermutations2.errors.txt b/tests/baselines/reference/promisePermutations2.errors.txt index 0fc6f04911a..b21a3a39208 100644 --- a/tests/baselines/reference/promisePermutations2.errors.txt +++ b/tests/baselines/reference/promisePermutations2.errors.txt @@ -25,8 +25,6 @@ tests/cases/compiler/promisePermutations2.ts(105,19): error TS2345: Argument of Types of parameters 'cb' and 'value' are incompatible. Type 'string' is not assignable to type '(a: T) => T'. tests/cases/compiler/promisePermutations2.ts(108,19): error TS2345: Argument of type '(cb: (a: T) => T) => IPromise' is not assignable to parameter of type '(value: string) => IPromise'. - Types of parameters 'cb' and 'value' are incompatible. - Type 'string' is not assignable to type '(a: T) => T'. tests/cases/compiler/promisePermutations2.ts(109,19): error TS2345: Argument of type '(cb: (a: T) => T) => Promise' is not assignable to parameter of type '(value: string) => Promise'. Types of parameters 'cb' and 'value' are incompatible. Type 'string' is not assignable to type '(a: T) => T'. @@ -228,8 +226,6 @@ tests/cases/compiler/promisePermutations2.ts(159,21): error TS2345: Argument of var s7a = r7.then(testFunction7, testFunction7, testFunction7); // error ~~~~~~~~~~~~~ !!! error TS2345: Argument of type '(cb: (a: T) => T) => IPromise' is not assignable to parameter of type '(value: string) => IPromise'. -!!! error TS2345: Types of parameters 'cb' and 'value' are incompatible. -!!! error TS2345: Type 'string' is not assignable to type '(a: T) => T'. var s7b = r7.then(testFunction7P, testFunction7P, testFunction7P); // error ~~~~~~~~~~~~~~ !!! error TS2345: Argument of type '(cb: (a: T) => T) => Promise' is not assignable to parameter of type '(value: string) => Promise'. diff --git a/tests/baselines/reference/promisePermutations3.errors.txt b/tests/baselines/reference/promisePermutations3.errors.txt index a1a1b3f493f..1a33d1083aa 100644 --- a/tests/baselines/reference/promisePermutations3.errors.txt +++ b/tests/baselines/reference/promisePermutations3.errors.txt @@ -28,8 +28,6 @@ tests/cases/compiler/promisePermutations3.ts(105,19): error TS2345: Argument of Types of parameters 'cb' and 'value' are incompatible. Type 'string' is not assignable to type '(a: T) => T'. tests/cases/compiler/promisePermutations3.ts(108,19): error TS2345: Argument of type '(cb: (a: T) => T) => IPromise' is not assignable to parameter of type '(value: string) => IPromise'. - Types of parameters 'cb' and 'value' are incompatible. - Type 'string' is not assignable to type '(a: T) => T'. tests/cases/compiler/promisePermutations3.ts(109,19): error TS2345: Argument of type '(cb: (a: T) => T) => Promise' is not assignable to parameter of type '(value: string) => Promise'. Types of parameters 'cb' and 'value' are incompatible. Type 'string' is not assignable to type '(a: T) => T'. @@ -240,8 +238,6 @@ tests/cases/compiler/promisePermutations3.ts(165,21): error TS2345: Argument of var s7a = r7.then(testFunction7, testFunction7, testFunction7); // error ~~~~~~~~~~~~~ !!! error TS2345: Argument of type '(cb: (a: T) => T) => IPromise' is not assignable to parameter of type '(value: string) => IPromise'. -!!! error TS2345: Types of parameters 'cb' and 'value' are incompatible. -!!! error TS2345: Type 'string' is not assignable to type '(a: T) => T'. var s7b = r7.then(testFunction7P, testFunction7P, testFunction7P); // error ~~~~~~~~~~~~~~ !!! error TS2345: Argument of type '(cb: (a: T) => T) => Promise' is not assignable to parameter of type '(value: string) => Promise'. diff --git a/tests/cases/compiler/instantiatedTypeAliasDisplay.ts b/tests/cases/compiler/instantiatedTypeAliasDisplay.ts new file mode 100644 index 00000000000..8f7500e7830 --- /dev/null +++ b/tests/cases/compiler/instantiatedTypeAliasDisplay.ts @@ -0,0 +1,17 @@ +// @declaration: true + +// Repros from #12066 + +interface X { + a: A; +} +interface Y { + b: B; +} +type Z = X | Y; + +declare function f1(): Z; +declare function f2(a: A, b: B, c: C, d: D): Z; + +const x1 = f1(); // Z +const x2 = f2({}, {}, {}, {}); // Z<{}, string[]> \ No newline at end of file