Merge branch 'master' into deferGenericFunctionInference

This commit is contained in:
Anders Hejlsberg
2019-02-28 17:14:11 -08:00
239 changed files with 25878 additions and 12475 deletions
+7 -2
View File
@@ -2499,8 +2499,13 @@ namespace ts {
declareSymbol(symbolTable, containingClass.symbol, node, SymbolFlags.Property, SymbolFlags.None, /*isReplaceableByMethod*/ true);
break;
case SyntaxKind.SourceFile:
// this.foo assignment in a source file
// Do not bind. It would be nice to support this someday though.
// this.property = assignment in a source file -- declare symbol in exports for a module, in locals for a script
if ((thisContainer as SourceFile).commonJsModuleIndicator) {
declareSymbol(thisContainer.symbol.exports!, thisContainer.symbol, node, SymbolFlags.Property | SymbolFlags.ExportValue, SymbolFlags.None);
}
else {
declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.FunctionScopedVariableExcludes);
}
break;
default:
+109 -36
View File
@@ -88,8 +88,16 @@ namespace ts {
const emitResolver = createResolver();
const nodeBuilder = createNodeBuilder();
const globals = createSymbolTable();
const undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined" as __String);
undefinedSymbol.declarations = [];
const globalThisSymbol = createSymbol(SymbolFlags.Module, "globalThis" as __String, CheckFlags.Readonly);
globalThisSymbol.exports = globals;
globalThisSymbol.valueDeclaration = createNode(SyntaxKind.Identifier) as Identifier;
(globalThisSymbol.valueDeclaration as Identifier).escapedText = "globalThis" as __String;
globals.set(globalThisSymbol.escapedName, globalThisSymbol);
const argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments" as __String);
const requireSymbol = createSymbol(SymbolFlags.Property, "require" as __String);
@@ -310,9 +318,9 @@ namespace ts {
getAccessibleSymbolChain,
getTypePredicateOfSignature: getTypePredicateOfSignature as (signature: Signature) => TypePredicate, // TODO: GH#18217
resolveExternalModuleSymbol,
tryGetThisTypeAt: node => {
tryGetThisTypeAt: (node, includeGlobalThis) => {
node = getParseTreeNode(node);
return node && tryGetThisTypeAt(node);
return node && tryGetThisTypeAt(node, includeGlobalThis);
},
getTypeArgumentConstraint: nodeIn => {
const node = getParseTreeNode(nodeIn, isTypeNode);
@@ -459,7 +467,6 @@ namespace ts {
const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true);
const globals = createSymbolTable();
interface DuplicateInfoForSymbol {
readonly firstFileLocations: Node[];
readonly secondFileLocations: Node[];
@@ -7407,7 +7414,7 @@ namespace ts {
const nameType = property.name && getLiteralTypeFromPropertyName(property.name);
const name = nameType && isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined;
const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name);
return !!expected && isLiteralType(expected) && !isTypeIdenticalTo(getTypeOfNode(property), expected);
return !!expected && isLiteralType(expected) && !isTypeAssignableTo(getTypeOfNode(property), expected);
});
}
@@ -7578,7 +7585,19 @@ namespace ts {
constraintDepth++;
let result = computeBaseConstraint(getSimplifiedType(t));
constraintDepth--;
if (!popTypeResolution() || nonTerminating) {
if (!popTypeResolution()) {
if (t.flags & TypeFlags.TypeParameter) {
const errorNode = getConstraintDeclaration(<TypeParameter>t);
if (errorNode) {
const diagnostic = error(errorNode, Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(t));
if (currentNode && !isNodeDescendantOf(errorNode, currentNode) && !isNodeDescendantOf(currentNode, errorNode)) {
addRelatedInfo(diagnostic, createDiagnosticForNode(currentNode, Diagnostics.Circularity_originates_in_type_at_this_location));
}
}
}
result = circularConstraintType;
}
if (nonTerminating) {
result = circularConstraintType;
}
t.immediateBaseConstraint = result || noConstraintType;
@@ -9704,7 +9723,7 @@ namespace ts {
}
function getLiteralTypeFromProperties(type: Type, include: TypeFlags) {
return getUnionType(map(getPropertiesOfType(type), t => getLiteralTypeFromProperty(t, include)));
return getUnionType(map(getPropertiesOfType(type), p => getLiteralTypeFromProperty(p, include)));
}
function getNonEnumNumberIndexInfo(type: Type) {
@@ -10679,12 +10698,29 @@ namespace ts {
return !!(<InferenceContext>mapper).typeParameters;
}
function cloneTypeMapper(mapper: TypeMapper): TypeMapper {
function cloneTypeMapper(mapper: TypeMapper, extraFlags: InferenceFlags = 0): TypeMapper {
return mapper && isInferenceContext(mapper) ?
createInferenceContext(mapper.typeParameters, mapper.signature, mapper.flags | InferenceFlags.NoDefault, mapper.compareTypes, mapper.inferences) :
createInferenceContext(mapper.typeParameters, mapper.signature, mapper.flags | extraFlags, mapper.compareTypes, mapper.inferences) :
mapper;
}
function cloneInferredPartOfContext(context: InferenceContext): InferenceContext | undefined {
// Filter context to only those parameters which actually have inference candidates
const params = [];
const inferences = [];
for (let i = 0; i < context.typeParameters.length; i++) {
const info = context.inferences[i];
if (info.candidates || info.contraCandidates) {
params.push(context.typeParameters[i]);
inferences.push(info);
}
}
if (!params.length) {
return undefined;
}
return createInferenceContext(params, context.signature, context.flags | InferenceFlags.NoDefault, context.compareTypes, inferences);
}
function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper;
function combineTypeMappers(mapper1: TypeMapper, mapper2: TypeMapper | undefined): TypeMapper;
function combineTypeMappers(mapper1: TypeMapper, mapper2: TypeMapper): TypeMapper {
@@ -12725,11 +12761,10 @@ namespace ts {
else if (source.flags & TypeFlags.Conditional) {
if (target.flags & TypeFlags.Conditional) {
// Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if
// they have the same distributivity, T1 and T2 are identical types, U1 and U2 are identical
// types, X1 is related to X2, and Y1 is related to Y2.
if ((<ConditionalType>source).root.isDistributive === (<ConditionalType>target).root.isDistributive &&
isTypeIdenticalTo((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType) &&
isTypeIdenticalTo((<ConditionalType>source).checkType, (<ConditionalType>target).checkType)) {
// one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2,
// and Y1 is related to Y2.
if (isTypeIdenticalTo((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType) &&
(isRelatedTo((<ConditionalType>source).checkType, (<ConditionalType>target).checkType) || isRelatedTo((<ConditionalType>target).checkType, (<ConditionalType>source).checkType))) {
if (result = isRelatedTo(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target), reportErrors)) {
result &= isRelatedTo(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target), reportErrors);
}
@@ -12824,12 +12859,22 @@ namespace ts {
}
return Ternary.False;
function isNonGeneric(type: Type) {
// If we're already in identity relationship checking, we should use `isRelatedTo`
// to catch the `Maybe` from an excessively deep type (which we then assume means
// that the type could possibly contain a generic)
if (relation === identityRelation) {
return isRelatedTo(type, getPermissiveInstantiation(type)) === Ternary.True;
}
return isTypeIdenticalTo(type, getPermissiveInstantiation(type));
}
function relateVariances(sourceTypeArguments: ReadonlyArray<Type> | undefined, targetTypeArguments: ReadonlyArray<Type> | undefined, variances: Variance[]) {
if (result = typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors)) {
return result;
}
const isCovariantVoid = targetTypeArguments && hasCovariantVoidArgument(targetTypeArguments, variances);
varianceCheckFailed = !isCovariantVoid;
const allowStructuralFallback = (targetTypeArguments && hasCovariantVoidArgument(targetTypeArguments, variances)) || isNonGeneric(source) || isNonGeneric(target);
varianceCheckFailed = !allowStructuralFallback;
// The type arguments did not relate appropriately, but it may be because we have no variance
// information (in which case typeArgumentsRelatedTo defaulted to covariance for all type
// arguments). It might also be the case that the target type has a 'void' type argument for
@@ -12837,7 +12882,7 @@ namespace ts {
// (in which case any type argument is permitted on the source side). In those cases we proceed
// with a structural comparison. Otherwise, we know for certain the instantiations aren't
// related and we can return here.
if (variances !== emptyArray && !isCovariantVoid) {
if (variances !== emptyArray && !allowStructuralFallback) {
// In some cases generic types that are covariant in regular type checking mode become
// invariant in --strictFunctionTypes mode because one or more type parameters are used in
// both co- and contravariant positions. In order to make it easier to diagnose *why* such
@@ -14885,7 +14930,7 @@ namespace ts {
// parameter should be instantiated to the empty object type.
inferredType = instantiateType(defaultType,
combineTypeMappers(
createBackreferenceMapper(context.signature!.typeParameters!, index),
createBackreferenceMapper(context.typeParameters, index),
context));
}
else {
@@ -16982,25 +17027,27 @@ namespace ts {
captureLexicalThis(node, container);
}
const type = tryGetThisTypeAt(node, container);
if (!type && noImplicitThis) {
// With noImplicitThis, functions may not reference 'this' if it has type 'any'
const diag = error(
node,
capturedByArrowFunction && container.kind === SyntaxKind.SourceFile ?
Diagnostics.The_containing_arrow_function_captures_the_global_value_of_this_which_implicitly_has_type_any :
Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation);
if (!isSourceFile(container)) {
const outsideThis = tryGetThisTypeAt(container);
if (outsideThis) {
addRelatedInfo(diag, createDiagnosticForNode(container, Diagnostics.An_outer_value_of_this_is_shadowed_by_this_container));
const type = tryGetThisTypeAt(node, /*includeGlobalThis*/ true, container);
if (noImplicitThis) {
const globalThisType = getTypeOfSymbol(globalThisSymbol);
if (type === globalThisType && capturedByArrowFunction) {
error(node, Diagnostics.The_containing_arrow_function_captures_the_global_value_of_this);
}
else if (!type) {
// With noImplicitThis, functions may not reference 'this' if it has type 'any'
const diag = error(node, Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation);
if (!isSourceFile(container)) {
const outsideThis = tryGetThisTypeAt(container);
if (outsideThis && outsideThis !== globalThisType) {
addRelatedInfo(diag, createDiagnosticForNode(container, Diagnostics.An_outer_value_of_this_is_shadowed_by_this_container));
}
}
}
}
return type || anyType;
}
function tryGetThisTypeAt(node: Node, container = getThisContainer(node, /*includeArrowFunctions*/ false)): Type | undefined {
function tryGetThisTypeAt(node: Node, includeGlobalThis = true, container = getThisContainer(node, /*includeArrowFunctions*/ false)): Type | undefined {
const isInJS = isInJSFile(node);
if (isFunctionLike(container) &&
(!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) {
@@ -17047,6 +17094,16 @@ namespace ts {
return getFlowTypeOfReference(node, type);
}
}
if (isSourceFile(container)) {
// look up in the source file's locals or exports
if (container.commonJsModuleIndicator) {
const fileSymbol = getSymbolOfNode(container);
return fileSymbol && getTypeOfSymbol(fileSymbol);
}
else if (includeGlobalThis) {
return getTypeOfSymbol(globalThisSymbol);
}
}
}
function getClassNameFromPrototypeMethod(container: Node) {
@@ -18841,7 +18898,7 @@ namespace ts {
const namespaceName = getJsxNamespace(location);
const resolvedNamespace = resolveName(location, namespaceName, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined, namespaceName, /*isUse*/ false);
if (resolvedNamespace) {
const candidate = getSymbol(getExportsOfSymbol(resolveSymbol(resolvedNamespace)), JsxNames.JSX, SymbolFlags.Namespace);
const candidate = resolveSymbol(getSymbol(getExportsOfSymbol(resolveSymbol(resolvedNamespace)), JsxNames.JSX, SymbolFlags.Namespace));
if (candidate) {
if (links) {
links.jsxNamespace = candidate;
@@ -19344,6 +19401,12 @@ namespace ts {
if (isJSLiteralType(leftType)) {
return anyType;
}
if (leftType.symbol === globalThisSymbol) {
if (noImplicitAny) {
error(right, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(leftType));
}
return anyType;
}
if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) {
reportNonexistentProperty(right, leftType.flags & TypeFlags.TypeParameter && (leftType as TypeParameter).isThisType ? apparentType : leftType);
}
@@ -20018,7 +20081,7 @@ namespace ts {
// We clone the contextual mapper to avoid disturbing a resolution in progress for an
// outer call expression. Effectively we just want a snapshot of whatever has been
// inferred for any outer call expression so far.
const instantiatedType = instantiateType(contextualType, cloneTypeMapper(getContextualMapper(node)));
const instantiatedType = instantiateType(contextualType, cloneTypeMapper(getContextualMapper(node), InferenceFlags.NoDefault));
// If the contextual type is a generic function type with a single call signature, we
// instantiate the type with its own type parameters and type arguments. This ensures that
// the type parameters are not erased to type any during type inference such that they can
@@ -20035,7 +20098,7 @@ namespace ts {
inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType);
// Create a type mapper for instantiating generic contextual types using the inferences made
// from the return type.
context.returnMapper = cloneTypeMapper(context);
context.returnMapper = cloneInferredPartOfContext(context);
}
}
@@ -21690,6 +21753,17 @@ namespace ts {
}
}
}
const restType = getEffectiveRestType(context);
if (restType && restType.flags & TypeFlags.TypeParameter) {
// The contextual signature has a generic rest parameter. We first instantiate the contextual
// signature (without fixing type parameters) and assign types to contextually typed parameters.
const instantiatedContext = instantiateSignature(context, cloneTypeMapper(mapper));
assignContextualParameterTypes(signature, instantiatedContext);
// We then infer from a tuple type representing the parameters that correspond to the contextual
// rest parameter.
const restPos = getParameterCount(context) - 1;
inferTypes((<InferenceContext>mapper).inferences, getRestTypeAtPosition(signature, restPos), restType);
}
}
function assignContextualParameterTypes(signature: Signature, context: Signature) {
@@ -23494,9 +23568,8 @@ namespace ts {
checkSourceElement(node.constraint);
checkSourceElement(node.default);
const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node));
if (!hasNonCircularBaseConstraint(typeParameter)) {
error(getEffectiveConstraintOfTypeParameter(node), Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(typeParameter));
}
// Resolve base constraint to reveal circularity errors
getBaseConstraintOfType(typeParameter);
if (!hasNonCircularTypeParameterDefault(typeParameter)) {
error(node.default, Diagnostics.Type_parameter_0_has_a_circular_default, typeToString(typeParameter));
}
+5 -1
View File
@@ -2589,6 +2589,10 @@
"category": "Error",
"code": 2750
},
"Circularity originates in type at this location.": {
"category": "Error",
"code": 2751
},
"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
@@ -4149,7 +4153,7 @@
"category": "Error",
"code": 7040
},
"The containing arrow function captures the global value of 'this' which implicitly has type 'any'.": {
"The containing arrow function captures the global value of 'this'.": {
"category": "Error",
"code": 7041
},
+11 -1
View File
@@ -615,7 +615,17 @@ namespace ts {
directoryExists,
createDirectory(directoryName: string) {
if (!nodeSystem.directoryExists(directoryName)) {
_fs.mkdirSync(directoryName);
// Wrapped in a try-catch to prevent crashing if we are in a race
// with another copy of ourselves to create the same directory
try {
_fs.mkdirSync(directoryName);
}
catch (e) {
if (e.code !== "EEXIST") {
// Failed for some other reason (access denied?); still throw
throw e;
}
}
}
},
getExecutingFilePath() {
+1 -1
View File
@@ -3223,7 +3223,7 @@ namespace ts {
*/
/* @internal */ resolveExternalModuleSymbol(symbol: Symbol): Symbol;
/** @param node A location where we might consider accessing `this`. Not necessarily a ThisExpression. */
/* @internal */ tryGetThisTypeAt(node: Node): Type | undefined;
/* @internal */ tryGetThisTypeAt(node: Node, includeGlobalThis?: boolean): Type | undefined;
/* @internal */ getTypeArgumentConstraint(node: TypeNode): Type | undefined;
/**
+13 -5
View File
@@ -774,7 +774,7 @@ namespace FourSlash {
if ("exact" in options) {
ts.Debug.assert(!("includes" in options) && !("excludes" in options));
if (options.exact === undefined) throw this.raiseError("Expected no completions");
this.verifyCompletionsAreExactly(actualCompletions.entries, toArray(options.exact));
this.verifyCompletionsAreExactly(actualCompletions.entries, toArray(options.exact), options.marker);
}
else {
if (options.includes) {
@@ -841,14 +841,14 @@ namespace FourSlash {
}
}
private verifyCompletionsAreExactly(actual: ReadonlyArray<ts.CompletionEntry>, expected: ReadonlyArray<FourSlashInterface.ExpectedCompletionEntry>) {
private verifyCompletionsAreExactly(actual: ReadonlyArray<ts.CompletionEntry>, expected: ReadonlyArray<FourSlashInterface.ExpectedCompletionEntry>, marker?: ArrayOrSingle<string | Marker>) {
// First pass: test that names are right. Then we'll test details.
assert.deepEqual(actual.map(a => a.name), expected.map(e => typeof e === "string" ? e : e.name));
assert.deepEqual(actual.map(a => a.name), expected.map(e => typeof e === "string" ? e : e.name), marker ? "At marker " + JSON.stringify(marker) : undefined);
ts.zipWith(actual, expected, (completion, expectedCompletion, index) => {
const name = typeof expectedCompletion === "string" ? expectedCompletion : expectedCompletion.name;
if (completion.name !== name) {
this.raiseError(`Expected completion at index ${index} to be ${name}, got ${completion.name}`);
this.raiseError(`${marker ? JSON.stringify(marker) : "" } Expected completion at index ${index} to be ${name}, got ${completion.name}`);
}
this.verifyCompletionEntry(completion, expectedCompletion);
});
@@ -4545,6 +4545,7 @@ namespace FourSlashInterface {
export function globalTypesPlus(plus: ReadonlyArray<ExpectedCompletionEntry>): ReadonlyArray<ExpectedCompletionEntry> {
return [
{ name: "globalThis", kind: "module" },
...globalTypeDecls,
...plus,
...typeKeywords,
@@ -4786,6 +4787,7 @@ namespace FourSlashInterface {
export const globalsInsideFunction = (plus: ReadonlyArray<ExpectedCompletionEntry>): ReadonlyArray<ExpectedCompletionEntry> => [
{ name: "arguments", kind: "local var" },
...plus,
{ name: "globalThis", kind: "module" },
...globalsVars,
{ name: "undefined", kind: "var" },
...globalKeywordsInsideFunction,
@@ -4921,13 +4923,19 @@ namespace FourSlashInterface {
})();
export const globals: ReadonlyArray<ExpectedCompletionEntryObject> = [
{ name: "globalThis", kind: "module" },
...globalsVars,
{ name: "undefined", kind: "var" },
...globalKeywords
];
export function globalsPlus(plus: ReadonlyArray<ExpectedCompletionEntry>): ReadonlyArray<ExpectedCompletionEntry> {
return [...globalsVars, ...plus, { name: "undefined", kind: "var" }, ...globalKeywords];
return [
{ name: "globalThis", kind: "module" },
...globalsVars,
...plus,
{ name: "undefined", kind: "var" },
...globalKeywords];
}
}
+1 -1
View File
@@ -1030,7 +1030,7 @@ namespace ts.Completions {
// Need to insert 'this.' before properties of `this` type, so only do that if `includeInsertTextCompletions`
if (preferences.includeCompletionsWithInsertText && scopeNode.kind !== SyntaxKind.SourceFile) {
const thisType = typeChecker.tryGetThisTypeAt(scopeNode);
const thisType = typeChecker.tryGetThisTypeAt(scopeNode, /*includeGlobalThis*/ false);
if (thisType) {
for (const symbol of getPropertiesForCompletion(thisType, typeChecker)) {
symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: SymbolOriginInfoKind.ThisType };
+1 -1
View File
@@ -310,7 +310,7 @@ namespace ts.SymbolDisplay {
displayParts.push(spacePart());
addFullSymbolName(symbol);
}
if (symbolFlags & SymbolFlags.Module) {
if (symbolFlags & SymbolFlags.Module && !isThisExpression) {
prefixNextMeaning();
const declaration = getDeclarationOfKind<ModuleDeclaration>(symbol, SyntaxKind.ModuleDeclaration);
const isNamespace = declaration && declaration.name && declaration.name.kind === SyntaxKind.Identifier;
@@ -708,7 +708,8 @@ namespace ts.projectSystem {
// Check identifiers defined in HTML content are available in .ts file
const project = configuredProjectAt(projectService, 0);
let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1, emptyOptions);
assert(completions && completions.entries[0].name === "hello", `expected entry hello to be in completion list`);
assert(completions && completions.entries[1].name === "hello", `expected entry hello to be in completion list`);
assert(completions && completions.entries[0].name === "globalThis", `first entry should be globalThis (not strictly relevant for this test).`);
// Close HTML file
projectService.applyChangesInOpenFiles(
@@ -1684,9 +1684,9 @@ namespace ts.projectSystem {
TI.getNpmCommandForInstallation(npmPath, tsVersion, packageNames, packageNames.length - Math.ceil(packageNames.length / 2)).command
];
it("works when the command is too long to install all packages at once", () => {
const commands: [string, string[]][] = [];
const hasError = TI.installNpmPackages(npmPath, tsVersion, packageNames, (file, args) => {
commands.push([file, args]);
const commands: string[] = [];
const hasError = TI.installNpmPackages(npmPath, tsVersion, packageNames, command => {
commands.push(command);
return false;
});
assert.isFalse(hasError);
@@ -1694,9 +1694,9 @@ namespace ts.projectSystem {
});
it("installs remaining packages when one of the partial command fails", () => {
const commands: [string, string[]][] = [];
const hasError = TI.installNpmPackages(npmPath, tsVersion, packageNames, (file, args) => {
commands.push([file, args]);
const commands: string[] = [];
const hasError = TI.installNpmPackages(npmPath, tsVersion, packageNames, command => {
commands.push(command);
return commands.length === 1;
});
assert.isTrue(hasError);
+12 -8
View File
@@ -70,10 +70,10 @@ namespace ts.server.typingsInstaller {
cwd: string;
encoding: "utf-8";
}
type ExecFileSync = (file: string, args: string[], options: ExecSyncOptions) => string;
type ExecSync = (command: string, options: ExecSyncOptions) => string;
export class NodeTypingsInstaller extends TypingsInstaller {
private readonly nodeExecFileSync: ExecFileSync;
private readonly nodeExecSync: ExecSync;
private readonly npmPath: string;
readonly typesRegistry: Map<MapLike<string>>;
@@ -89,11 +89,15 @@ namespace ts.server.typingsInstaller {
log);
this.npmPath = npmLocation !== undefined ? npmLocation : getDefaultNPMLocation(process.argv[0]);
// If the NPM path contains spaces and isn't wrapped in quotes, do so.
if (stringContains(this.npmPath, " ") && this.npmPath[0] !== `"`) {
this.npmPath = `"${this.npmPath}"`;
}
if (this.log.isEnabled()) {
this.log.writeLine(`Process id: ${process.pid}`);
this.log.writeLine(`NPM location: ${this.npmPath} (explicit '${Arguments.NpmLocation}' ${npmLocation === undefined ? "not " : ""} provided)`);
}
({ execFileSync: this.nodeExecFileSync } = require("child_process"));
({ execSync: this.nodeExecSync } = require("child_process"));
this.ensurePackageDirectoryExists(globalTypingsCacheLocation);
@@ -101,7 +105,7 @@ namespace ts.server.typingsInstaller {
if (this.log.isEnabled()) {
this.log.writeLine(`Updating ${typesRegistryPackageName} npm package...`);
}
this.execFileSyncAndLog(this.npmPath, ["install", "--ignore-scripts", `${typesRegistryPackageName}@${this.latestDistTag}`], { cwd: globalTypingsCacheLocation });
this.execSyncAndLog(`${this.npmPath} install --ignore-scripts ${typesRegistryPackageName}@${this.latestDistTag}`, { cwd: globalTypingsCacheLocation });
if (this.log.isEnabled()) {
this.log.writeLine(`Updated ${typesRegistryPackageName} npm package`);
}
@@ -185,7 +189,7 @@ namespace ts.server.typingsInstaller {
this.log.writeLine(`#${requestId} with arguments'${JSON.stringify(packageNames)}'.`);
}
const start = Date.now();
const hasError = installNpmPackages(this.npmPath, version, packageNames, (file, args) => this.execFileSyncAndLog(file, args, { cwd }));
const hasError = installNpmPackages(this.npmPath, version, packageNames, command => this.execSyncAndLog(command, { cwd }));
if (this.log.isEnabled()) {
this.log.writeLine(`npm install #${requestId} took: ${Date.now() - start} ms`);
}
@@ -193,12 +197,12 @@ namespace ts.server.typingsInstaller {
}
/** Returns 'true' in case of error. */
private execFileSyncAndLog(file: string, args: string[], options: Pick<ExecSyncOptions, "cwd">): boolean {
private execSyncAndLog(command: string, options: Pick<ExecSyncOptions, "cwd">): boolean {
if (this.log.isEnabled()) {
this.log.writeLine(`Exec: ${file} ${args.join(" ")}`);
this.log.writeLine(`Exec: ${command}`);
}
try {
const stdout = this.nodeExecFileSync(file, args, { ...options, encoding: "utf-8" });
const stdout = this.nodeExecSync(command, { ...options, encoding: "utf-8" });
if (this.log.isEnabled()) {
this.log.writeLine(` Succeeded. stdout:${indent(sys.newLine, stdout)}`);
}
+5 -12
View File
@@ -31,35 +31,28 @@ namespace ts.server.typingsInstaller {
}
/*@internal*/
export function installNpmPackages(npmPath: string, tsVersion: string, packageNames: string[], install: (file: string, args: string[]) => boolean) {
export function installNpmPackages(npmPath: string, tsVersion: string, packageNames: string[], install: (command: string) => boolean) {
let hasError = false;
for (let remaining = packageNames.length; remaining > 0;) {
const result = getNpmCommandForInstallation(npmPath, tsVersion, packageNames, remaining);
remaining = result.remaining;
hasError = install(result.command[0], result.command[1]) || hasError;
hasError = install(result.command) || hasError;
}
return hasError;
}
function getUserAgent(tsVersion: string) {
return `--user-agent="typesInstaller/${tsVersion}"`;
}
const npmInstall = "install", ignoreScripts = "--ignore-scripts", saveDev = "--save-dev";
const commandBaseLength = npmInstall.length + ignoreScripts.length + saveDev.length + getUserAgent("").length + 5;
/*@internal*/
export function getNpmCommandForInstallation(npmPath: string, tsVersion: string, packageNames: string[], remaining: number) {
const sliceStart = packageNames.length - remaining;
let packages: string[], toSlice = remaining;
let command: string, toSlice = remaining;
while (true) {
packages = toSlice === packageNames.length ? packageNames : packageNames.slice(sliceStart, sliceStart + toSlice);
const commandLength = npmPath.length + commandBaseLength + packages.join(" ").length + tsVersion.length;
if (commandLength < 8000) {
command = `${npmPath} install --ignore-scripts ${(toSlice === packageNames.length ? packageNames : packageNames.slice(sliceStart, sliceStart + toSlice)).join(" ")} --save-dev --user-agent="typesInstaller/${tsVersion}"`;
if (command.length < 8000) {
break;
}
toSlice = toSlice - Math.floor(toSlice / 2);
}
const command: [string, string[]] = [npmPath, [npmInstall, ignoreScripts, ...packages, saveDev, getUserAgent(tsVersion)]];
return { command, remaining: remaining - toSlice };
}