mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge branch 'master' into mappedTypes
# Conflicts: # src/compiler/checker.ts
This commit is contained in:
+66
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+25
-18
@@ -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 && !(<AnonymousType>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> = () => [X, Y];
|
||||
// export type Foo<Y> = Bar<any, Y>;
|
||||
// export const y = (x: Foo<string>) => 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<Signature>());
|
||||
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<T extends Type>(type: T, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): T {
|
||||
const instantiations = mapper.instantiations || (mapper.instantiations = []);
|
||||
return <T>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 ((<ObjectType>type).objectFlags & ObjectFlags.Reference) {
|
||||
return createTypeReference((<TypeReference>type).target, instantiateList((<TypeReference>type).typeArguments, mapper, instantiateType));
|
||||
return createTypeReference((<TypeReference>type).target, instantiateTypes((<TypeReference>type).typeArguments, mapper));
|
||||
}
|
||||
}
|
||||
if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Primitive)) {
|
||||
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*subtypeReduction*/ false, type.aliasSymbol, mapper.targetTypes);
|
||||
return getUnionType(instantiateTypes((<UnionType>type).types, mapper), /*subtypeReduction*/ false, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
|
||||
}
|
||||
if (type.flags & TypeFlags.Intersection) {
|
||||
return getIntersectionType(instantiateList((<IntersectionType>type).types, mapper, instantiateType), type.aliasSymbol, mapper.targetTypes);
|
||||
return getIntersectionType(instantiateTypes((<IntersectionType>type).types, mapper), type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
|
||||
}
|
||||
if (type.flags & TypeFlags.Index) {
|
||||
return getIndexType(instantiateType((<IndexType>type).type, mapper));
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
+9
-4
@@ -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<string>(_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")) {
|
||||
|
||||
@@ -2202,6 +2202,7 @@ namespace ts {
|
||||
|
||||
/* @internal */ getFileProcessingDiagnostics(): DiagnosticCollection;
|
||||
/* @internal */ getResolvedTypeReferenceDirectives(): Map<ResolvedTypeReferenceDirective>;
|
||||
/* @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<Signature>; // 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).
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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<void>(),
|
||||
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", () => {
|
||||
|
||||
@@ -8,44 +8,44 @@ namespace ts.projectSystem {
|
||||
interface InstallerParams {
|
||||
globalTypingsCacheLocation?: string;
|
||||
throttleLimit?: number;
|
||||
typesRegistry?: Map<void>;
|
||||
}
|
||||
|
||||
function createTypesRegistry(...list: string[]): Map<void> {
|
||||
const map = createMap<void>();
|
||||
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<string>) {
|
||||
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<string>) {
|
||||
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]);
|
||||
});
|
||||
});
|
||||
}
|
||||
Vendored
+2
-2
@@ -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;
|
||||
|
||||
Vendored
+2
-2
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
+54
-23
@@ -1,4 +1,5 @@
|
||||
/// <reference types="node" />
|
||||
/// <reference path="shared.ts" />
|
||||
/// <reference path="session.ts" />
|
||||
// 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");
|
||||
|
||||
@@ -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*/
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/// <reference path="types.d.ts" />
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
"files": [
|
||||
"../services/shims.ts",
|
||||
"../services/utilities.ts",
|
||||
"shared.ts",
|
||||
"utilities.ts",
|
||||
"scriptVersionCache.ts",
|
||||
"scriptInfo.ts",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"files": [
|
||||
"../services/shims.ts",
|
||||
"../services/utilities.ts",
|
||||
"shared.ts",
|
||||
"utilities.ts",
|
||||
"scriptVersionCache.ts",
|
||||
"scriptInfo.ts",
|
||||
|
||||
Vendored
+19
-9
@@ -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<string>;
|
||||
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<string>;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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<void>;
|
||||
}
|
||||
|
||||
function loadTypesRegistryFile(typesRegistryFilePath: string, host: InstallTypingHost, log: Log): Map<void> {
|
||||
if (!host.fileExists(typesRegistryFilePath)) {
|
||||
if (log.isEnabled()) {
|
||||
log.writeLine(`Types registry file '${typesRegistryFilePath}' does not exist`);
|
||||
}
|
||||
return createMap<void>();
|
||||
}
|
||||
try {
|
||||
const content = <TypesRegistryFile>JSON.parse(host.readFile(typesRegistryFilePath));
|
||||
return createMap<void>(content.entries);
|
||||
}
|
||||
catch (e) {
|
||||
if (log.isEnabled()) {
|
||||
log.writeLine(`Error when loading types registry file '${typesRegistryFilePath}': ${(<Error>e).message}, ${(<Error>e).stack}`);
|
||||
}
|
||||
return createMap<void>();
|
||||
}
|
||||
}
|
||||
|
||||
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<void>;
|
||||
|
||||
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: ${(<Error>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();
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
},
|
||||
"files": [
|
||||
"../types.d.ts",
|
||||
"../shared.ts",
|
||||
"typingsInstaller.ts",
|
||||
"nodeTypingsInstaller.ts"
|
||||
]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/// <reference path="../../compiler/moduleNameResolver.ts" />
|
||||
/// <reference path="../../services/jsTyping.ts"/>
|
||||
/// <reference path="../types.d.ts"/>
|
||||
/// <reference path="../shared.ts"/>
|
||||
|
||||
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<void>;
|
||||
|
||||
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(<TypingsInstallEvent>{
|
||||
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<true> = createMap<true>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
/// <reference path="types.d.ts" />
|
||||
/// <reference path="shared.ts" />
|
||||
|
||||
namespace ts.server {
|
||||
export enum LogLevel {
|
||||
@@ -10,6 +11,7 @@ namespace ts.server {
|
||||
|
||||
export const emptyArray: ReadonlyArray<any> = [];
|
||||
|
||||
|
||||
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<string>, cachePath?: string): DiscoverTypings {
|
||||
return {
|
||||
projectName: project.getProjectName(),
|
||||
fileNames: project.getFileNames(),
|
||||
fileNames: project.getFileNames(/*excludeFilesFromExternalLibraries*/ true),
|
||||
compilerOptions: project.getCompilerOptions(),
|
||||
typingOptions,
|
||||
unresolvedImports,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user