mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge branch 'master' into incrementalBuildInfo
This commit is contained in:
+242
-107
@@ -228,9 +228,9 @@ namespace ts {
|
||||
isContextSensitive,
|
||||
getFullyQualifiedName,
|
||||
getResolvedSignature: (node, candidatesOutArray, agumentCount) =>
|
||||
getResolvedSignatureWorker(node, candidatesOutArray, agumentCount, /*isForSignatureHelp*/ false),
|
||||
getResolvedSignatureWorker(node, candidatesOutArray, agumentCount, CheckMode.Normal),
|
||||
getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, agumentCount) =>
|
||||
getResolvedSignatureWorker(node, candidatesOutArray, agumentCount, /*isForSignatureHelp*/ true),
|
||||
getResolvedSignatureWorker(node, candidatesOutArray, agumentCount, CheckMode.IsForSignatureHelp),
|
||||
getConstantValue: nodeIn => {
|
||||
const node = getParseTreeNode(nodeIn, canHaveConstantValue);
|
||||
return node ? getConstantValue(node) : undefined;
|
||||
@@ -374,10 +374,10 @@ namespace ts {
|
||||
getLocalTypeParametersOfClassOrInterfaceOrTypeAlias,
|
||||
};
|
||||
|
||||
function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, isForSignatureHelp: boolean): Signature | undefined {
|
||||
function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined {
|
||||
const node = getParseTreeNode(nodeIn, isCallLikeExpression);
|
||||
apparentArgumentCount = argumentCount;
|
||||
const res = node ? getResolvedSignature(node, candidatesOutArray, isForSignatureHelp) : undefined;
|
||||
const res = node ? getResolvedSignature(node, candidatesOutArray, checkMode) : undefined;
|
||||
apparentArgumentCount = undefined;
|
||||
return res;
|
||||
}
|
||||
@@ -688,10 +688,12 @@ namespace ts {
|
||||
}
|
||||
|
||||
const enum CheckMode {
|
||||
Normal = 0, // Normal type checking
|
||||
SkipContextSensitive = 1, // Skip context sensitive function expressions
|
||||
Inferential = 2, // Inferential typing
|
||||
Contextual = 3, // Normal type checking informed by a contextual type, therefore not cacheable
|
||||
Normal = 0, // Normal type checking
|
||||
Contextual = 1 << 0, // Explicitly assigned contextual type, therefore not cacheable
|
||||
Inferential = 1 << 1, // Inferential typing
|
||||
SkipContextSensitive = 1 << 2, // Skip context sensitive function expressions
|
||||
SkipGenericFunctions = 1 << 3, // Skip single signature generic functions
|
||||
IsForSignatureHelp = 1 << 4, // Call resolution for purposes of signature help
|
||||
}
|
||||
|
||||
const enum CallbackCheck {
|
||||
@@ -3981,7 +3983,7 @@ namespace ts {
|
||||
context.flags &= ~NodeBuilderFlags.WriteTypeParametersInQualifiedName; // Avoids potential infinite loop when building for a claimspace with a generic
|
||||
const shouldUseGeneratedName =
|
||||
context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams &&
|
||||
type.symbol.declarations[0] &&
|
||||
type.symbol.declarations && type.symbol.declarations[0] &&
|
||||
isTypeParameterDeclaration(type.symbol.declarations[0]) &&
|
||||
typeParameterShadowsNameInScope(type, context);
|
||||
const name = shouldUseGeneratedName
|
||||
@@ -8373,9 +8375,23 @@ namespace ts {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getSignatureInstantiation(signature: Signature, typeArguments: Type[] | undefined, isJavascript: boolean): Signature {
|
||||
return getSignatureInstantiationWithoutFillingInTypeArguments(signature, fillMissingTypeArguments(typeArguments, signature.typeParameters, getMinTypeArgumentCount(signature.typeParameters), isJavascript));
|
||||
function getSignatureInstantiation(signature: Signature, typeArguments: Type[] | undefined, isJavascript: boolean, inferredTypeParameters?: ReadonlyArray<TypeParameter>): Signature {
|
||||
const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, fillMissingTypeArguments(typeArguments, signature.typeParameters, getMinTypeArgumentCount(signature.typeParameters), isJavascript));
|
||||
if (inferredTypeParameters) {
|
||||
const returnSignature = getSingleCallSignature(getReturnTypeOfSignature(instantiatedSignature));
|
||||
if (returnSignature) {
|
||||
const newReturnSignature = cloneSignature(returnSignature);
|
||||
newReturnSignature.typeParameters = inferredTypeParameters;
|
||||
newReturnSignature.target = returnSignature.target;
|
||||
newReturnSignature.mapper = returnSignature.mapper;
|
||||
const newInstantiatedSignature = cloneSignature(instantiatedSignature);
|
||||
newInstantiatedSignature.resolvedReturnType = getOrCreateTypeFromSignature(newReturnSignature);
|
||||
return newInstantiatedSignature;
|
||||
}
|
||||
}
|
||||
return instantiatedSignature;
|
||||
}
|
||||
|
||||
function getSignatureInstantiationWithoutFillingInTypeArguments(signature: Signature, typeArguments: ReadonlyArray<Type> | undefined): Signature {
|
||||
const instantiations = signature.instantiations || (signature.instantiations = createMap<Signature>());
|
||||
const id = getTypeListId(typeArguments);
|
||||
@@ -8389,6 +8405,7 @@ namespace ts {
|
||||
function createSignatureInstantiation(signature: Signature, typeArguments: ReadonlyArray<Type> | undefined): Signature {
|
||||
return instantiateSignature(signature, createSignatureTypeMapper(signature, typeArguments), /*eraseTypeParameters*/ true);
|
||||
}
|
||||
|
||||
function createSignatureTypeMapper(signature: Signature, typeArguments: ReadonlyArray<Type> | undefined): TypeMapper {
|
||||
return createTypeMapper(signature.typeParameters!, typeArguments);
|
||||
}
|
||||
@@ -8790,7 +8807,7 @@ namespace ts {
|
||||
|
||||
function getTypeReferenceTypeWorker(node: NodeWithTypeArguments, symbol: Symbol, typeArguments: Type[] | undefined): Type | undefined {
|
||||
if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface)) {
|
||||
if (symbol.valueDeclaration && isBinaryExpression(symbol.valueDeclaration.parent)) {
|
||||
if (symbol.valueDeclaration && symbol.valueDeclaration.parent && isBinaryExpression(symbol.valueDeclaration.parent)) {
|
||||
const jsdocType = getJSDocTypeReference(node, symbol, typeArguments);
|
||||
if (jsdocType) {
|
||||
return jsdocType;
|
||||
@@ -20035,27 +20052,36 @@ namespace ts {
|
||||
// Instantiate a generic signature in the context of a non-generic signature (section 3.8.5 in TypeScript spec)
|
||||
function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, contextualMapper?: TypeMapper, compareTypes?: TypeComparer): Signature {
|
||||
const context = createInferenceContext(signature.typeParameters!, signature, InferenceFlags.None, compareTypes);
|
||||
const sourceSignature = contextualMapper ? instantiateSignature(contextualSignature, contextualMapper) : contextualSignature;
|
||||
// We clone the contextualMapper to avoid fixing. For example, when the source signature is <T>(x: T) => T[] and
|
||||
// the contextual signature is (...args: A) => B, we want to infer the element type of A's constraint (say 'any')
|
||||
// for T but leave it possible to later infer '[any]' back to A.
|
||||
const restType = getEffectiveRestType(contextualSignature);
|
||||
const mapper = contextualMapper && restType && restType.flags & TypeFlags.TypeParameter ? cloneTypeMapper(contextualMapper) : contextualMapper;
|
||||
const sourceSignature = mapper ? instantiateSignature(contextualSignature, mapper) : contextualSignature;
|
||||
forEachMatchingParameterType(sourceSignature, signature, (source, target) => {
|
||||
// Type parameters from outer context referenced by source type are fixed by instantiation of the source type
|
||||
inferTypes(context.inferences, source, target);
|
||||
});
|
||||
if (!contextualMapper) {
|
||||
inferTypes(context.inferences, getReturnTypeOfSignature(contextualSignature), getReturnTypeOfSignature(signature), InferencePriority.ReturnType);
|
||||
const signaturePredicate = getTypePredicateOfSignature(signature);
|
||||
const contextualPredicate = getTypePredicateOfSignature(sourceSignature);
|
||||
if (signaturePredicate && contextualPredicate && signaturePredicate.kind === contextualPredicate.kind &&
|
||||
(signaturePredicate.kind === TypePredicateKind.This || signaturePredicate.parameterIndex === (contextualPredicate as IdentifierTypePredicate).parameterIndex)) {
|
||||
inferTypes(context.inferences, contextualPredicate.type, signaturePredicate.type, InferencePriority.ReturnType);
|
||||
}
|
||||
}
|
||||
return getSignatureInstantiation(signature, getInferredTypes(context), isInJSFile(contextualSignature.declaration));
|
||||
}
|
||||
|
||||
function inferJsxTypeArguments(node: JsxOpeningLikeElement, signature: Signature, excludeArgument: ReadonlyArray<boolean> | undefined, context: InferenceContext): Type[] {
|
||||
function inferJsxTypeArguments(node: JsxOpeningLikeElement, signature: Signature, checkMode: CheckMode, context: InferenceContext): Type[] {
|
||||
const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node);
|
||||
|
||||
const checkAttrType = checkExpressionWithContextualType(node.attributes, paramType, excludeArgument && excludeArgument[0] !== undefined ? identityMapper : context);
|
||||
const checkAttrType = checkExpressionWithContextualType(node.attributes, paramType, context, checkMode);
|
||||
inferTypes(context.inferences, checkAttrType, paramType);
|
||||
|
||||
return getInferredTypes(context);
|
||||
}
|
||||
|
||||
function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: ReadonlyArray<Expression>, excludeArgument: ReadonlyArray<boolean> | undefined, context: InferenceContext): Type[] {
|
||||
function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: ReadonlyArray<Expression>, checkMode: CheckMode, context: InferenceContext): Type[] {
|
||||
// Clear out all the inference results from the last time inferTypeArguments was called on this context
|
||||
for (const inference of context.inferences) {
|
||||
// As an optimization, we don't have to clear (and later recompute) inferred types
|
||||
@@ -20068,7 +20094,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
if (isJsxOpeningLikeElement(node)) {
|
||||
return inferJsxTypeArguments(node, signature, excludeArgument, context);
|
||||
return inferJsxTypeArguments(node, signature, checkMode, context);
|
||||
}
|
||||
|
||||
// If a contextual type is available, infer from that type to the return type of the call expression. For
|
||||
@@ -20115,10 +20141,7 @@ namespace ts {
|
||||
const arg = args[i];
|
||||
if (arg.kind !== SyntaxKind.OmittedExpression) {
|
||||
const paramType = getTypeAtPosition(signature, i);
|
||||
// For context sensitive arguments we pass the identityMapper, which is a signal to treat all
|
||||
// context sensitive function expressions as wildcards
|
||||
const mapper = excludeArgument && excludeArgument[i] !== undefined ? identityMapper : context;
|
||||
const argType = checkExpressionWithContextualType(arg, paramType, mapper);
|
||||
const argType = checkExpressionWithContextualType(arg, paramType, context, checkMode);
|
||||
inferTypes(context.inferences, argType, paramType);
|
||||
}
|
||||
}
|
||||
@@ -20146,7 +20169,7 @@ namespace ts {
|
||||
// and the argument are ...x forms.
|
||||
return arg.kind === SyntaxKind.SyntheticExpression ?
|
||||
createArrayType((<SyntheticExpression>arg).type) :
|
||||
getArrayifiedType(checkExpressionWithContextualType((<SpreadElement>arg).expression, restType, context));
|
||||
getArrayifiedType(checkExpressionWithContextualType((<SpreadElement>arg).expression, restType, context, CheckMode.Normal));
|
||||
}
|
||||
}
|
||||
const contextualType = getIndexTypeOfType(restType, IndexKind.Number) || anyType;
|
||||
@@ -20154,7 +20177,7 @@ namespace ts {
|
||||
const types = [];
|
||||
let spreadIndex = -1;
|
||||
for (let i = index; i < argCount; i++) {
|
||||
const argType = checkExpressionWithContextualType(args[i], contextualType, context);
|
||||
const argType = checkExpressionWithContextualType(args[i], contextualType, context, CheckMode.Normal);
|
||||
if (spreadIndex < 0 && isSpreadArgument(args[i])) {
|
||||
spreadIndex = i - index;
|
||||
}
|
||||
@@ -20212,14 +20235,13 @@ namespace ts {
|
||||
* @param node a JSX opening-like element we are trying to figure its call signature
|
||||
* @param signature a candidate signature we are trying whether it is a call signature
|
||||
* @param relation a relationship to check parameter and argument type
|
||||
* @param excludeArgument
|
||||
*/
|
||||
function checkApplicableSignatureForJsxOpeningLikeElement(node: JsxOpeningLikeElement, signature: Signature, relation: Map<RelationComparisonResult>, excludeArgument: boolean[] | undefined, reportErrors: boolean) {
|
||||
function checkApplicableSignatureForJsxOpeningLikeElement(node: JsxOpeningLikeElement, signature: Signature, relation: Map<RelationComparisonResult>, checkMode: CheckMode, reportErrors: boolean) {
|
||||
// Stateless function components can have maximum of three arguments: "props", "context", and "updater".
|
||||
// However "context" and "updater" are implicit and can't be specify by users. Only the first parameter, props,
|
||||
// can be specified by users through attributes property.
|
||||
const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node);
|
||||
const attributesType = checkExpressionWithContextualType(node.attributes, paramType, excludeArgument && excludeArgument[0] ? identityMapper : undefined);
|
||||
const attributesType = checkExpressionWithContextualType(node.attributes, paramType, /*contextualMapper*/ undefined, checkMode);
|
||||
return checkTypeRelatedToAndOptionallyElaborate(attributesType, paramType, relation, reportErrors ? node.tagName : undefined, node.attributes);
|
||||
}
|
||||
|
||||
@@ -20228,10 +20250,10 @@ namespace ts {
|
||||
args: ReadonlyArray<Expression>,
|
||||
signature: Signature,
|
||||
relation: Map<RelationComparisonResult>,
|
||||
excludeArgument: boolean[] | undefined,
|
||||
checkMode: CheckMode,
|
||||
reportErrors: boolean) {
|
||||
if (isJsxOpeningLikeElement(node)) {
|
||||
return checkApplicableSignatureForJsxOpeningLikeElement(node, signature, relation, excludeArgument, reportErrors);
|
||||
return checkApplicableSignatureForJsxOpeningLikeElement(node, signature, relation, checkMode, reportErrors);
|
||||
}
|
||||
const thisType = getThisTypeOfSignature(signature);
|
||||
if (thisType && thisType !== voidType && node.kind !== SyntaxKind.NewExpression) {
|
||||
@@ -20253,11 +20275,11 @@ namespace ts {
|
||||
const arg = args[i];
|
||||
if (arg.kind !== SyntaxKind.OmittedExpression) {
|
||||
const paramType = getTypeAtPosition(signature, i);
|
||||
const argType = checkExpressionWithContextualType(arg, paramType, excludeArgument && excludeArgument[i] ? identityMapper : undefined);
|
||||
// If one or more arguments are still excluded (as indicated by a non-null excludeArgument parameter),
|
||||
const argType = checkExpressionWithContextualType(arg, paramType, /*contextualMapper*/ undefined, checkMode);
|
||||
// If one or more arguments are still excluded (as indicated by CheckMode.SkipContextSensitive),
|
||||
// we obtain the regular type of any object literal arguments because we may not have inferred complete
|
||||
// parameter types yet and therefore excess property checks may yield false positives (see #17041).
|
||||
const checkArgType = excludeArgument ? getRegularTypeOfObjectLiteral(argType) : argType;
|
||||
const checkArgType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(argType) : argType;
|
||||
if (!checkTypeRelatedToAndOptionallyElaborate(checkArgType, paramType, relation, reportErrors ? arg : undefined, arg, headMessage)) {
|
||||
return false;
|
||||
}
|
||||
@@ -20473,7 +20495,7 @@ namespace ts {
|
||||
return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount);
|
||||
}
|
||||
|
||||
function resolveCall(node: CallLikeExpression, signatures: ReadonlyArray<Signature>, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean, fallbackError?: DiagnosticMessage): Signature {
|
||||
function resolveCall(node: CallLikeExpression, signatures: ReadonlyArray<Signature>, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, fallbackError?: DiagnosticMessage): Signature {
|
||||
const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression;
|
||||
const isDecorator = node.kind === SyntaxKind.Decorator;
|
||||
const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node);
|
||||
@@ -20515,7 +20537,7 @@ namespace ts {
|
||||
// For a decorator, no arguments are susceptible to contextual typing due to the fact
|
||||
// decorators are applied to a declaration by the emitter, and not to an expression.
|
||||
const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters;
|
||||
let excludeArgument = !isDecorator && !isSingleNonGenericCandidate ? getExcludeArgument(args) : undefined;
|
||||
let argCheckMode = !isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive) ? CheckMode.SkipContextSensitive : CheckMode.Normal;
|
||||
|
||||
// The following variables are captured and modified by calls to chooseOverload.
|
||||
// If overload resolution or type argument inference fails, we want to report the
|
||||
@@ -20546,7 +20568,7 @@ namespace ts {
|
||||
// If we are in signature help, a trailing comma indicates that we intend to provide another argument,
|
||||
// so we will only accept overloads with arity at least 1 higher than the current number of provided arguments.
|
||||
const signatureHelpTrailingComma =
|
||||
isForSignatureHelp && node.kind === SyntaxKind.CallExpression && node.arguments.hasTrailingComma;
|
||||
!!(checkMode & CheckMode.IsForSignatureHelp) && node.kind === SyntaxKind.CallExpression && node.arguments.hasTrailingComma;
|
||||
|
||||
// Section 4.12.1:
|
||||
// if the candidate list contains one or more signatures for which the type of each argument
|
||||
@@ -20574,12 +20596,7 @@ namespace ts {
|
||||
// skip the checkApplicableSignature check.
|
||||
if (reportErrors) {
|
||||
if (candidateForArgumentError) {
|
||||
// excludeArgument is undefined, in this case also equivalent to [undefined, undefined, ...]
|
||||
// The importance of excludeArgument is to prevent us from typing function expression parameters
|
||||
// in arguments too early. If possible, we'd like to only type them once we know the correct
|
||||
// overload. However, this matters for the case where the call is correct. When the call is
|
||||
// an error, we don't need to exclude any arguments, although it would cause no harm to do so.
|
||||
checkApplicableSignature(node, args, candidateForArgumentError, assignableRelation, /*excludeArgument*/ undefined, /*reportErrors*/ true);
|
||||
checkApplicableSignature(node, args, candidateForArgumentError, assignableRelation, CheckMode.Normal, /*reportErrors*/ true);
|
||||
}
|
||||
else if (candidateForArgumentArityError) {
|
||||
diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args));
|
||||
@@ -20613,7 +20630,7 @@ namespace ts {
|
||||
if (typeArguments || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) {
|
||||
return undefined;
|
||||
}
|
||||
if (!checkApplicableSignature(node, args, candidate, relation, excludeArgument, /*reportErrors*/ false)) {
|
||||
if (!checkApplicableSignature(node, args, candidate, relation, CheckMode.Normal, /*reportErrors*/ false)) {
|
||||
candidateForArgumentError = candidate;
|
||||
return undefined;
|
||||
}
|
||||
@@ -20640,9 +20657,10 @@ namespace ts {
|
||||
}
|
||||
else {
|
||||
inferenceContext = createInferenceContext(candidate.typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None);
|
||||
typeArgumentTypes = inferTypeArguments(node, candidate, args, excludeArgument, inferenceContext);
|
||||
typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext);
|
||||
argCheckMode |= inferenceContext.flags & InferenceFlags.SkippedGenericFunction ? CheckMode.SkipGenericFunctions : CheckMode.Normal;
|
||||
}
|
||||
checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration));
|
||||
checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters);
|
||||
// If the original signature has a generic rest type, instantiation may produce a
|
||||
// signature with different arity and we need to perform another arity check.
|
||||
if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) {
|
||||
@@ -20653,21 +20671,21 @@ namespace ts {
|
||||
else {
|
||||
checkCandidate = candidate;
|
||||
}
|
||||
if (!checkApplicableSignature(node, args, checkCandidate, relation, excludeArgument, /*reportErrors*/ false)) {
|
||||
if (!checkApplicableSignature(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false)) {
|
||||
// Give preference to error candidates that have no rest parameters (as they are more specific)
|
||||
if (!candidateForArgumentError || getEffectiveRestType(candidateForArgumentError) || !getEffectiveRestType(checkCandidate)) {
|
||||
candidateForArgumentError = checkCandidate;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (excludeArgument) {
|
||||
if (argCheckMode) {
|
||||
// If one or more context sensitive arguments were excluded, we start including
|
||||
// them now (and keeping do so for any subsequent candidates) and perform a second
|
||||
// round of type inference and applicability checking for this particular candidate.
|
||||
excludeArgument = undefined;
|
||||
argCheckMode = CheckMode.Normal;
|
||||
if (inferenceContext) {
|
||||
const typeArgumentTypes = inferTypeArguments(node, candidate, args, excludeArgument, inferenceContext);
|
||||
checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration));
|
||||
const typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext);
|
||||
checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters);
|
||||
// If the original signature has a generic rest type, instantiation may produce a
|
||||
// signature with different arity and we need to perform another arity check.
|
||||
if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) {
|
||||
@@ -20675,7 +20693,7 @@ namespace ts {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!checkApplicableSignature(node, args, checkCandidate, relation, excludeArgument, /*reportErrors*/ false)) {
|
||||
if (!checkApplicableSignature(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false)) {
|
||||
// Give preference to error candidates that have no rest parameters (as they are more specific)
|
||||
if (!candidateForArgumentError || getEffectiveRestType(candidateForArgumentError) || !getEffectiveRestType(checkCandidate)) {
|
||||
candidateForArgumentError = checkCandidate;
|
||||
@@ -20691,21 +20709,6 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function getExcludeArgument(args: ReadonlyArray<Expression>): boolean[] | undefined {
|
||||
let excludeArgument: boolean[] | undefined;
|
||||
// We do not need to call `getEffectiveArgumentCount` here as it only
|
||||
// applies when calculating the number of arguments for a decorator.
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (isContextSensitive(args[i])) {
|
||||
if (!excludeArgument) {
|
||||
excludeArgument = new Array(args.length);
|
||||
}
|
||||
excludeArgument[i] = true;
|
||||
}
|
||||
}
|
||||
return excludeArgument;
|
||||
}
|
||||
|
||||
// No signature was applicable. We have already reported the errors for the invalid signature.
|
||||
// If this is a type resolution session, e.g. Language Service, try to get better information than anySignature.
|
||||
function getCandidateForOverloadFailure(
|
||||
@@ -20805,7 +20808,7 @@ namespace ts {
|
||||
|
||||
function inferSignatureInstantiationForOverloadFailure(node: CallLikeExpression, typeParameters: ReadonlyArray<TypeParameter>, candidate: Signature, args: ReadonlyArray<Expression>): Signature {
|
||||
const inferenceContext = createInferenceContext(typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None);
|
||||
const typeArgumentTypes = inferTypeArguments(node, candidate, args, getExcludeArgument(args), inferenceContext);
|
||||
const typeArgumentTypes = inferTypeArguments(node, candidate, args, CheckMode.SkipContextSensitive | CheckMode.SkipGenericFunctions, inferenceContext);
|
||||
return createSignatureInstantiation(candidate, typeArgumentTypes);
|
||||
}
|
||||
|
||||
@@ -20828,7 +20831,7 @@ namespace ts {
|
||||
return maxParamsIndex;
|
||||
}
|
||||
|
||||
function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
|
||||
function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature {
|
||||
if (node.expression.kind === SyntaxKind.SuperKeyword) {
|
||||
const superType = checkSuperExpression(node.expression);
|
||||
if (isTypeAny(superType)) {
|
||||
@@ -20843,7 +20846,7 @@ namespace ts {
|
||||
const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!);
|
||||
if (baseTypeNode) {
|
||||
const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode);
|
||||
return resolveCall(node, baseConstructors, candidatesOutArray, isForSignatureHelp);
|
||||
return resolveCall(node, baseConstructors, candidatesOutArray, checkMode);
|
||||
}
|
||||
}
|
||||
return resolveUntypedCall(node);
|
||||
@@ -20903,12 +20906,32 @@ namespace ts {
|
||||
}
|
||||
return resolveErrorCall(node);
|
||||
}
|
||||
// When a call to a generic function is an argument to an outer call to a generic function for which
|
||||
// inference is in process, we have a choice to make. If the inner call relies on inferences made from
|
||||
// its contextual type to its return type, deferring the inner call processing allows the best possible
|
||||
// contextual type to accumulate. But if the outer call relies on inferences made from the return type of
|
||||
// the inner call, the inner call should be processed early. There's no sure way to know which choice is
|
||||
// right (only a full unification algorithm can determine that), so we resort to the following heuristic:
|
||||
// If no type arguments are specified in the inner call and at least one call signature is generic and
|
||||
// returns a function type, we choose to defer processing. This narrowly permits function composition
|
||||
// operators to flow inferences through return types, but otherwise processes calls right away. We
|
||||
// use the resolvingSignature singleton to indicate that we deferred processing. This result will be
|
||||
// propagated out and eventually turned into silentNeverType (a type that is assignable to anything and
|
||||
// from which we never make inferences).
|
||||
if (checkMode & CheckMode.SkipGenericFunctions && !node.typeArguments && callSignatures.some(isGenericFunctionReturningFunction)) {
|
||||
skippedGenericFunction(node, checkMode);
|
||||
return resolvingSignature;
|
||||
}
|
||||
// If the function is explicitly marked with `@class`, then it must be constructed.
|
||||
if (callSignatures.some(sig => isInJSFile(sig.declaration) && !!getJSDocClassTag(sig.declaration!))) {
|
||||
error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType));
|
||||
return resolveErrorCall(node);
|
||||
}
|
||||
return resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp);
|
||||
return resolveCall(node, callSignatures, candidatesOutArray, checkMode);
|
||||
}
|
||||
|
||||
function isGenericFunctionReturningFunction(signature: Signature) {
|
||||
return !!(signature.typeParameters && isFunctionType(getReturnTypeOfSignature(signature)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -20922,7 +20945,7 @@ namespace ts {
|
||||
!numCallSignatures && !numConstructSignatures && !(apparentFuncType.flags & (TypeFlags.Union | TypeFlags.Never)) && isTypeAssignableTo(funcType, globalFunctionType);
|
||||
}
|
||||
|
||||
function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
|
||||
function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature {
|
||||
if (node.arguments && languageVersion < ScriptTarget.ES5) {
|
||||
const spreadIndex = getSpreadArgumentIndex(node.arguments);
|
||||
if (spreadIndex >= 0) {
|
||||
@@ -20975,7 +20998,7 @@ namespace ts {
|
||||
return resolveErrorCall(node);
|
||||
}
|
||||
|
||||
return resolveCall(node, constructSignatures, candidatesOutArray, isForSignatureHelp);
|
||||
return resolveCall(node, constructSignatures, candidatesOutArray, checkMode);
|
||||
}
|
||||
|
||||
// If expressionType's apparent type is an object type with no construct signatures but
|
||||
@@ -20984,7 +21007,7 @@ namespace ts {
|
||||
// operation is Any. It is an error to have a Void this type.
|
||||
const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call);
|
||||
if (callSignatures.length) {
|
||||
const signature = resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp);
|
||||
const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode);
|
||||
if (!noImplicitAny) {
|
||||
if (signature.declaration && !isJSConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) {
|
||||
error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword);
|
||||
@@ -21094,7 +21117,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
|
||||
function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature {
|
||||
const tagType = checkExpression(node.tag);
|
||||
const apparentType = getApparentType(tagType);
|
||||
|
||||
@@ -21115,7 +21138,7 @@ namespace ts {
|
||||
return resolveErrorCall(node);
|
||||
}
|
||||
|
||||
return resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp);
|
||||
return resolveCall(node, callSignatures, candidatesOutArray, checkMode);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21146,7 +21169,7 @@ namespace ts {
|
||||
/**
|
||||
* Resolves a decorator as if it were a call expression.
|
||||
*/
|
||||
function resolveDecorator(node: Decorator, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
|
||||
function resolveDecorator(node: Decorator, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature {
|
||||
const funcType = checkExpression(node.expression);
|
||||
const apparentType = getApparentType(funcType);
|
||||
if (apparentType === errorType) {
|
||||
@@ -21175,7 +21198,7 @@ namespace ts {
|
||||
return resolveErrorCall(node);
|
||||
}
|
||||
|
||||
return resolveCall(node, callSignatures, candidatesOutArray, isForSignatureHelp, headMessage);
|
||||
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, headMessage);
|
||||
}
|
||||
|
||||
function createSignatureForJSXIntrinsic(node: JsxOpeningLikeElement, result: Type): Signature {
|
||||
@@ -21204,11 +21227,11 @@ namespace ts {
|
||||
);
|
||||
}
|
||||
|
||||
function resolveJsxOpeningLikeElement(node: JsxOpeningLikeElement, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
|
||||
function resolveJsxOpeningLikeElement(node: JsxOpeningLikeElement, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature {
|
||||
if (isJsxIntrinsicIdentifier(node.tagName)) {
|
||||
const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node);
|
||||
const fakeSignature = createSignatureForJSXIntrinsic(node, result);
|
||||
checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*mapper*/ undefined), result, node.tagName, node.attributes);
|
||||
checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*mapper*/ undefined, CheckMode.Normal), result, node.tagName, node.attributes);
|
||||
return fakeSignature;
|
||||
}
|
||||
const exprTypes = checkExpression(node.tagName);
|
||||
@@ -21228,7 +21251,7 @@ namespace ts {
|
||||
return resolveErrorCall(node);
|
||||
}
|
||||
|
||||
return resolveCall(node, signatures, candidatesOutArray, isForSignatureHelp);
|
||||
return resolveCall(node, signatures, candidatesOutArray, checkMode);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21243,19 +21266,19 @@ namespace ts {
|
||||
signature.parameters.length < getDecoratorArgumentCount(decorator, signature));
|
||||
}
|
||||
|
||||
function resolveSignature(node: CallLikeExpression, candidatesOutArray: Signature[] | undefined, isForSignatureHelp: boolean): Signature {
|
||||
function resolveSignature(node: CallLikeExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.CallExpression:
|
||||
return resolveCallExpression(node, candidatesOutArray, isForSignatureHelp);
|
||||
return resolveCallExpression(node, candidatesOutArray, checkMode);
|
||||
case SyntaxKind.NewExpression:
|
||||
return resolveNewExpression(node, candidatesOutArray, isForSignatureHelp);
|
||||
return resolveNewExpression(node, candidatesOutArray, checkMode);
|
||||
case SyntaxKind.TaggedTemplateExpression:
|
||||
return resolveTaggedTemplateExpression(node, candidatesOutArray, isForSignatureHelp);
|
||||
return resolveTaggedTemplateExpression(node, candidatesOutArray, checkMode);
|
||||
case SyntaxKind.Decorator:
|
||||
return resolveDecorator(node, candidatesOutArray, isForSignatureHelp);
|
||||
return resolveDecorator(node, candidatesOutArray, checkMode);
|
||||
case SyntaxKind.JsxOpeningElement:
|
||||
case SyntaxKind.JsxSelfClosingElement:
|
||||
return resolveJsxOpeningLikeElement(node, candidatesOutArray, isForSignatureHelp);
|
||||
return resolveJsxOpeningLikeElement(node, candidatesOutArray, checkMode);
|
||||
}
|
||||
throw Debug.assertNever(node, "Branch in 'resolveSignature' should be unreachable.");
|
||||
}
|
||||
@@ -21267,7 +21290,7 @@ namespace ts {
|
||||
* the function will fill it up with appropriate candidate signatures
|
||||
* @return a signature of the call-like expression or undefined if one can't be found
|
||||
*/
|
||||
function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[] | undefined, isForSignatureHelp = false): Signature {
|
||||
function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[] | undefined, checkMode?: CheckMode): Signature {
|
||||
const links = getNodeLinks(node);
|
||||
// If getResolvedSignature has already been called, we will have cached the resolvedSignature.
|
||||
// However, it is possible that either candidatesOutArray was not passed in the first time,
|
||||
@@ -21278,11 +21301,15 @@ namespace ts {
|
||||
return cached;
|
||||
}
|
||||
links.resolvedSignature = resolvingSignature;
|
||||
const result = resolveSignature(node, candidatesOutArray, isForSignatureHelp);
|
||||
// If signature resolution originated in control flow type analysis (for example to compute the
|
||||
// assigned type in a flow assignment) we don't cache the result as it may be based on temporary
|
||||
// types from the control flow analysis.
|
||||
links.resolvedSignature = flowLoopStart === flowLoopCount ? result : cached;
|
||||
const result = resolveSignature(node, candidatesOutArray, checkMode || CheckMode.Normal);
|
||||
// When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call
|
||||
// resolution should be deferred.
|
||||
if (result !== resolvingSignature) {
|
||||
// If signature resolution originated in control flow type analysis (for example to compute the
|
||||
// assigned type in a flow assignment) we don't cache the result as it may be based on temporary
|
||||
// types from the control flow analysis.
|
||||
links.resolvedSignature = flowLoopStart === flowLoopCount ? result : cached;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -21366,10 +21393,15 @@ namespace ts {
|
||||
* @param node The call/new expression to be checked.
|
||||
* @returns On success, the expression's signature's return type. On failure, anyType.
|
||||
*/
|
||||
function checkCallExpression(node: CallExpression | NewExpression): Type {
|
||||
function checkCallExpression(node: CallExpression | NewExpression, checkMode?: CheckMode): Type {
|
||||
if (!checkGrammarTypeArguments(node, node.typeArguments)) checkGrammarArguments(node.arguments);
|
||||
|
||||
const signature = getResolvedSignature(node);
|
||||
const signature = getResolvedSignature(node, /*candidatesOutArray*/ undefined, checkMode);
|
||||
if (signature === resolvingSignature) {
|
||||
// CheckMode.SkipGenericFunctions is enabled and this is a call to a generic function that
|
||||
// returns a function type. We defer checking and return anyFunctionType.
|
||||
return silentNeverType;
|
||||
}
|
||||
|
||||
if (node.expression.kind === SyntaxKind.SuperKeyword) {
|
||||
return voidType;
|
||||
@@ -21870,7 +21902,7 @@ namespace ts {
|
||||
const functionFlags = getFunctionFlags(func);
|
||||
let type: Type;
|
||||
if (func.body.kind !== SyntaxKind.Block) {
|
||||
type = checkExpressionCached(func.body, checkMode);
|
||||
type = checkExpressionCached(func.body, checkMode && checkMode & ~CheckMode.SkipGenericFunctions);
|
||||
if (functionFlags & FunctionFlags.Async) {
|
||||
// From within an async function you can return either a non-promise value or a promise. Any
|
||||
// Promise/A+ compatible implementation will always assimilate any foreign promise, so the
|
||||
@@ -22060,7 +22092,7 @@ namespace ts {
|
||||
forEachReturnStatement(<Block>func.body, returnStatement => {
|
||||
const expr = returnStatement.expression;
|
||||
if (expr) {
|
||||
let type = checkExpressionCached(expr, checkMode);
|
||||
let type = checkExpressionCached(expr, checkMode && checkMode & ~CheckMode.SkipGenericFunctions);
|
||||
if (functionFlags & FunctionFlags.Async) {
|
||||
// From within an async function you can return either a non-promise value or a promise. Any
|
||||
// Promise/A+ compatible implementation will always assimilate any foreign promise, so the
|
||||
@@ -22160,7 +22192,7 @@ namespace ts {
|
||||
checkNodeDeferred(node);
|
||||
|
||||
// The identityMapper object is used to indicate that function expressions are wildcards
|
||||
if (checkMode === CheckMode.SkipContextSensitive && isContextSensitive(node)) {
|
||||
if (checkMode && checkMode & CheckMode.SkipContextSensitive && isContextSensitive(node)) {
|
||||
// Skip parameters, return signature with return type that retains noncontextual parts so inferences can still be drawn in an early stage
|
||||
if (!getEffectiveReturnTypeNode(node) && hasContextSensitiveReturnExpression(node)) {
|
||||
const links = getNodeLinks(node);
|
||||
@@ -22200,7 +22232,7 @@ namespace ts {
|
||||
const signature = getSignaturesOfType(type, SignatureKind.Call)[0];
|
||||
if (isContextSensitive(node)) {
|
||||
const contextualMapper = getContextualMapper(node);
|
||||
if (checkMode === CheckMode.Inferential) {
|
||||
if (checkMode && checkMode & CheckMode.Inferential) {
|
||||
inferFromAnnotatedParameters(signature, contextualSignature, contextualMapper);
|
||||
}
|
||||
const instantiatedContextualSignature = contextualMapper === identityMapper ?
|
||||
@@ -23211,15 +23243,13 @@ namespace ts {
|
||||
return node;
|
||||
}
|
||||
|
||||
function checkExpressionWithContextualType(node: Expression, contextualType: Type, contextualMapper: TypeMapper | undefined): Type {
|
||||
function checkExpressionWithContextualType(node: Expression, contextualType: Type, contextualMapper: TypeMapper | undefined, checkMode: CheckMode): Type {
|
||||
const context = getContextNode(node);
|
||||
const saveContextualType = context.contextualType;
|
||||
const saveContextualMapper = context.contextualMapper;
|
||||
context.contextualType = contextualType;
|
||||
context.contextualMapper = contextualMapper;
|
||||
const checkMode = contextualMapper === identityMapper ? CheckMode.SkipContextSensitive :
|
||||
contextualMapper ? CheckMode.Inferential : CheckMode.Contextual;
|
||||
const type = checkExpression(node, checkMode);
|
||||
const type = checkExpression(node, checkMode | CheckMode.Contextual | (contextualMapper ? CheckMode.Inferential : 0));
|
||||
// We strip literal freshness when an appropriate contextual type is present such that contextually typed
|
||||
// literals always preserve their literal types (otherwise they might widen during type inference). An alternative
|
||||
// here would be to not mark contextually typed literals as fresh in the first place.
|
||||
@@ -23233,7 +23263,7 @@ namespace ts {
|
||||
function checkExpressionCached(node: Expression, checkMode?: CheckMode): Type {
|
||||
const links = getNodeLinks(node);
|
||||
if (!links.resolvedType) {
|
||||
if (checkMode) {
|
||||
if (checkMode && checkMode !== CheckMode.Normal) {
|
||||
return checkExpression(node, checkMode);
|
||||
}
|
||||
// When computing a type that we're going to cache, we need to ignore any ongoing control flow
|
||||
@@ -23341,22 +23371,127 @@ namespace ts {
|
||||
}
|
||||
|
||||
function instantiateTypeWithSingleGenericCallSignature(node: Expression | MethodDeclaration | QualifiedName, type: Type, checkMode?: CheckMode) {
|
||||
if (checkMode === CheckMode.Inferential) {
|
||||
if (checkMode && checkMode & (CheckMode.Inferential | CheckMode.SkipGenericFunctions)) {
|
||||
const signature = getSingleCallSignature(type);
|
||||
if (signature && signature.typeParameters) {
|
||||
if (checkMode & CheckMode.SkipGenericFunctions) {
|
||||
skippedGenericFunction(node, checkMode);
|
||||
return anyFunctionType;
|
||||
}
|
||||
const contextualType = getApparentTypeOfContextualType(<Expression>node);
|
||||
if (contextualType) {
|
||||
const contextualSignature = getSingleCallSignature(getNonNullableType(contextualType));
|
||||
if (contextualSignature && !contextualSignature.typeParameters) {
|
||||
return getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, getContextualMapper(node)));
|
||||
const context = <InferenceContext>getContextualMapper(node);
|
||||
// We have an expression that is an argument of a generic function for which we are performing
|
||||
// type argument inference. The expression is of a function type with a single generic call
|
||||
// signature and a contextual function type with a single non-generic call signature. Now check
|
||||
// if the outer function returns a function type with a single non-generic call signature and
|
||||
// if some of the outer function type parameters have no inferences so far. If so, we can
|
||||
// potentially add inferred type parameters to the outer function return type.
|
||||
const returnSignature = context.signature && getSingleCallSignature(getReturnTypeOfSignature(context.signature));
|
||||
if (returnSignature && !returnSignature.typeParameters && !every(context.inferences, hasInferenceCandidates)) {
|
||||
// Instantiate the expression type with its own type parameters as type arguments. This
|
||||
// ensures that the type parameters are not erased to type any during type inference such
|
||||
// that they can be inferred as actual types.
|
||||
const uniqueTypeParameters = getUniqueTypeParameters(context, signature.typeParameters);
|
||||
const strippedType = getOrCreateTypeFromSignature(getSignatureInstantiationWithoutFillingInTypeArguments(signature, uniqueTypeParameters));
|
||||
// Infer from the stripped expression type to the contextual type starting with an empty
|
||||
// set of inference candidates.
|
||||
const inferences = map(context.typeParameters, createInferenceInfo);
|
||||
inferTypes(inferences, strippedType, contextualType);
|
||||
// If we produced some inference candidates and if the type parameters for which we produced
|
||||
// candidates do not already have existing inferences, we adopt the new inference candidates and
|
||||
// add the type parameters of the expression type to the set of inferred type parameters for
|
||||
// the outer function return type.
|
||||
if (some(inferences, hasInferenceCandidates) && !hasOverlappingInferences(context.inferences, inferences)) {
|
||||
mergeInferences(context.inferences, inferences);
|
||||
context.inferredTypeParameters = concatenate(context.inferredTypeParameters, uniqueTypeParameters);
|
||||
return strippedType;
|
||||
}
|
||||
}
|
||||
return getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, context));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
function skippedGenericFunction(node: Node, checkMode: CheckMode) {
|
||||
if (checkMode & CheckMode.Inferential) {
|
||||
// We have skipped a generic function during inferential typing. Obtain the inference context and
|
||||
// indicate this has occurred such that we know a second pass of inference is be needed.
|
||||
const context = <InferenceContext>getContextualMapper(node);
|
||||
context.flags |= InferenceFlags.SkippedGenericFunction;
|
||||
}
|
||||
}
|
||||
|
||||
function hasInferenceCandidates(info: InferenceInfo) {
|
||||
return !!(info.candidates || info.contraCandidates);
|
||||
}
|
||||
|
||||
function hasOverlappingInferences(a: InferenceInfo[], b: InferenceInfo[]) {
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (hasInferenceCandidates(a[i]) && hasInferenceCandidates(b[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function mergeInferences(target: InferenceInfo[], source: InferenceInfo[]) {
|
||||
for (let i = 0; i < target.length; i++) {
|
||||
if (!hasInferenceCandidates(target[i]) && hasInferenceCandidates(source[i])) {
|
||||
target[i] = source[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getUniqueTypeParameters(context: InferenceContext, typeParameters: ReadonlyArray<TypeParameter>): ReadonlyArray<TypeParameter> {
|
||||
const result: TypeParameter[] = [];
|
||||
let oldTypeParameters: TypeParameter[] | undefined;
|
||||
let newTypeParameters: TypeParameter[] | undefined;
|
||||
for (const tp of typeParameters) {
|
||||
const name = tp.symbol.escapedName;
|
||||
if (hasTypeParameterByName(context.inferredTypeParameters, name) || hasTypeParameterByName(result, name)) {
|
||||
const newName = getUniqueTypeParameterName(concatenate(context.inferredTypeParameters, result), name);
|
||||
const symbol = createSymbol(SymbolFlags.TypeParameter, newName);
|
||||
const newTypeParameter = createTypeParameter(symbol);
|
||||
newTypeParameter.target = tp;
|
||||
oldTypeParameters = append(oldTypeParameters, tp);
|
||||
newTypeParameters = append(newTypeParameters, newTypeParameter);
|
||||
result.push(newTypeParameter);
|
||||
}
|
||||
else {
|
||||
result.push(tp);
|
||||
}
|
||||
}
|
||||
if (newTypeParameters) {
|
||||
const mapper = createTypeMapper(oldTypeParameters!, newTypeParameters);
|
||||
for (const tp of newTypeParameters) {
|
||||
tp.mapper = mapper;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function hasTypeParameterByName(typeParameters: ReadonlyArray<TypeParameter> | undefined, name: __String) {
|
||||
return some(typeParameters, tp => tp.symbol.escapedName === name);
|
||||
}
|
||||
|
||||
function getUniqueTypeParameterName(typeParameters: ReadonlyArray<TypeParameter>, baseName: __String) {
|
||||
let len = (<string>baseName).length;
|
||||
while (len > 1 && (<string>baseName).charCodeAt(len - 1) >= CharacterCodes._0 && (<string>baseName).charCodeAt(len - 1) <= CharacterCodes._9) len--;
|
||||
const s = (<string>baseName).slice(0, len);
|
||||
for (let index = 1; true; index++) {
|
||||
const augmentedName = <__String>(s + index);
|
||||
if (!hasTypeParameterByName(typeParameters, augmentedName)) {
|
||||
return augmentedName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of an expression. Unlike checkExpression, this function is simply concerned
|
||||
* with computing the type and may not fully check all contained sub-expressions for errors.
|
||||
@@ -23496,7 +23631,7 @@ namespace ts {
|
||||
}
|
||||
/* falls through */
|
||||
case SyntaxKind.NewExpression:
|
||||
return checkCallExpression(<CallExpression>node);
|
||||
return checkCallExpression(<CallExpression>node, checkMode);
|
||||
case SyntaxKind.TaggedTemplateExpression:
|
||||
return checkTaggedTemplateExpression(<TaggedTemplateExpression>node);
|
||||
case SyntaxKind.ParenthesizedExpression:
|
||||
|
||||
+16
-1
@@ -917,7 +917,7 @@ namespace ts {
|
||||
|
||||
/**
|
||||
* Deduplicates an unsorted array.
|
||||
* @param equalityComparer An optional `EqualityComparer` used to determine if two values are duplicates.
|
||||
* @param equalityComparer An `EqualityComparer` used to determine if two values are duplicates.
|
||||
* @param comparer An optional `Comparer` used to sort entries before comparison, though the
|
||||
* result will remain in the original order in `array`.
|
||||
*/
|
||||
@@ -1173,6 +1173,21 @@ namespace ts {
|
||||
}};
|
||||
}
|
||||
|
||||
export function arrayReverseIterator<T>(array: ReadonlyArray<T>): Iterator<T> {
|
||||
let i = array.length;
|
||||
return {
|
||||
next: () => {
|
||||
if (i === 0) {
|
||||
return { value: undefined as never, done: true };
|
||||
}
|
||||
else {
|
||||
i--;
|
||||
return { value: array[i], done: false };
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stable sort of an array. Elements equal to each other maintain their relative position in the array.
|
||||
*/
|
||||
|
||||
@@ -2927,6 +2927,10 @@
|
||||
"category": "Error",
|
||||
"code": 4102
|
||||
},
|
||||
"Type parameter '{0}' of exported mapped object type is using private name '{1}'.": {
|
||||
"category": "Error",
|
||||
"code": 4103
|
||||
},
|
||||
|
||||
"The current host does not support the '{0}' option.": {
|
||||
"category": "Error",
|
||||
@@ -4910,5 +4914,9 @@
|
||||
"Enable the 'experimentalDecorators' option in your configuration file": {
|
||||
"category": "Message",
|
||||
"code": 95074
|
||||
},
|
||||
"Convert to named parameters": {
|
||||
"category": "Message",
|
||||
"code": 95075
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,8 +71,8 @@ namespace ts {
|
||||
nonRecursive?: boolean;
|
||||
}
|
||||
|
||||
export function isPathInNodeModulesStartingWithDot(path: Path) {
|
||||
return stringContains(path, "/node_modules/.");
|
||||
export function isPathIgnored(path: Path) {
|
||||
return some(ignoredPaths, searchPath => stringContains(path, searchPath));
|
||||
}
|
||||
|
||||
export const maxNumberOfFilesToIterateForInvalidation = 256;
|
||||
@@ -696,7 +696,7 @@ namespace ts {
|
||||
}
|
||||
else {
|
||||
// If something to do with folder/file starting with "." in node_modules folder, skip it
|
||||
if (isPathInNodeModulesStartingWithDot(fileOrDirectoryPath)) return false;
|
||||
if (isPathIgnored(fileOrDirectoryPath)) return false;
|
||||
|
||||
// Some file or directory in the watching directory is created
|
||||
// Return early if it does not have any of the watching extension or not the custom failed lookup path
|
||||
|
||||
+16
-1
@@ -326,6 +326,9 @@ namespace ts {
|
||||
: FileWatcherEventKind.Changed;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export const ignoredPaths = ["/node_modules/.", "/.git"];
|
||||
|
||||
/*@internal*/
|
||||
export interface RecursiveDirectoryWatcherHost {
|
||||
watchDirectory: HostWatchDirectory;
|
||||
@@ -371,6 +374,8 @@ namespace ts {
|
||||
else {
|
||||
directoryWatcher = {
|
||||
watcher: host.watchDirectory(dirName, fileName => {
|
||||
if (isIgnoredPath(fileName)) return;
|
||||
|
||||
// Call the actual callback
|
||||
callbackCache.forEach((callbacks, rootDirName) => {
|
||||
if (rootDirName === dirPath || (startsWith(dirPath, rootDirName) && dirPath[rootDirName.length] === directorySeparator)) {
|
||||
@@ -426,7 +431,7 @@ namespace ts {
|
||||
const childFullName = getNormalizedAbsolutePath(child, parentDir);
|
||||
// Filter our the symbolic link directories since those arent included in recursive watch
|
||||
// which is same behaviour when recursive: true is passed to fs.watch
|
||||
return filePathComparer(childFullName, normalizePath(host.realpath(childFullName))) === Comparison.EqualTo ? childFullName : undefined;
|
||||
return !isIgnoredPath(childFullName) && filePathComparer(childFullName, normalizePath(host.realpath(childFullName))) === Comparison.EqualTo ? childFullName : undefined;
|
||||
}) : emptyArray,
|
||||
existingChildWatches,
|
||||
(child, childWatcher) => filePathComparer(child, childWatcher.dirName),
|
||||
@@ -452,6 +457,16 @@ namespace ts {
|
||||
(newChildWatches || (newChildWatches = [])).push(childWatcher);
|
||||
}
|
||||
}
|
||||
|
||||
function isIgnoredPath(path: string) {
|
||||
return some(ignoredPaths, searchPath => isInPath(path, searchPath));
|
||||
}
|
||||
|
||||
function isInPath(path: string, searchPath: string) {
|
||||
if (stringContains(path, searchPath)) return true;
|
||||
if (host.useCaseSensitiveFileNames) return false;
|
||||
return stringContains(toCanonicalFilePath(path), searchPath);
|
||||
}
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
|
||||
@@ -391,6 +391,10 @@ namespace ts {
|
||||
diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_interface_has_or_is_using_private_name_1;
|
||||
break;
|
||||
|
||||
case SyntaxKind.MappedType:
|
||||
diagnosticMessage = Diagnostics.Type_parameter_0_of_exported_mapped_object_type_is_using_private_name_1;
|
||||
break;
|
||||
|
||||
case SyntaxKind.ConstructorType:
|
||||
case SyntaxKind.ConstructSignature:
|
||||
diagnosticMessage = Diagnostics.Type_parameter_0_of_constructor_signature_from_exported_interface_has_or_is_using_private_name_1;
|
||||
|
||||
@@ -4423,6 +4423,7 @@ namespace ts {
|
||||
NoDefault = 1 << 0, // Infer unknownType for no inferences (otherwise anyType or emptyObjectType)
|
||||
AnyDefault = 1 << 1, // Infer anyType for no inferences (otherwise emptyObjectType)
|
||||
NoFixing = 1 << 2, // Disable type parameter fixing
|
||||
SkippedGenericFunction = 1 << 3,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4452,6 +4453,7 @@ namespace ts {
|
||||
flags: InferenceFlags; // Inference flags
|
||||
compareTypes: TypeComparer; // Type comparer function
|
||||
returnMapper?: TypeMapper; // Type mapper for inferences from return types (if any)
|
||||
inferredTypeParameters?: ReadonlyArray<TypeParameter>;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
|
||||
@@ -987,7 +987,7 @@ namespace ts {
|
||||
}
|
||||
nextSourceFileVersion(fileOrDirectoryPath);
|
||||
|
||||
if (isPathInNodeModulesStartingWithDot(fileOrDirectoryPath)) return;
|
||||
if (isPathIgnored(fileOrDirectoryPath)) return;
|
||||
|
||||
// If the the added or created file or directory is not supported file name, ignore the file
|
||||
// But when watched directory is added/removed, we need to reload the file list
|
||||
|
||||
+9
-372
@@ -47,23 +47,6 @@ interface XMLHttpRequest {
|
||||
/* tslint:enable:no-var-keyword prefer-const */
|
||||
|
||||
namespace Utils {
|
||||
// Setup some globals based on the current environment
|
||||
export const enum ExecutionEnvironment {
|
||||
Node,
|
||||
Browser,
|
||||
}
|
||||
|
||||
export function getExecutionEnvironment() {
|
||||
if (typeof window !== "undefined") {
|
||||
return ExecutionEnvironment.Browser;
|
||||
}
|
||||
else {
|
||||
return ExecutionEnvironment.Node;
|
||||
}
|
||||
}
|
||||
|
||||
export let currentExecutionEnvironment = getExecutionEnvironment();
|
||||
|
||||
export function encodeString(s: string): string {
|
||||
return ts.sys.bufferFrom!(s).toString("utf8");
|
||||
}
|
||||
@@ -74,23 +57,12 @@ namespace Utils {
|
||||
}
|
||||
|
||||
export function evalFile(fileContents: string, fileName: string, nodeContext?: any) {
|
||||
const environment = getExecutionEnvironment();
|
||||
switch (environment) {
|
||||
case ExecutionEnvironment.Browser:
|
||||
// tslint:disable-next-line:no-eval
|
||||
eval(fileContents);
|
||||
break;
|
||||
case ExecutionEnvironment.Node:
|
||||
const vm = require("vm");
|
||||
if (nodeContext) {
|
||||
vm.runInNewContext(fileContents, nodeContext, fileName);
|
||||
}
|
||||
else {
|
||||
vm.runInThisContext(fileContents, fileName);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unknown context");
|
||||
const vm = require("vm");
|
||||
if (nodeContext) {
|
||||
vm.runInNewContext(fileContents, nodeContext, fileName);
|
||||
}
|
||||
else {
|
||||
vm.runInThisContext(fileContents, fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,344 +596,11 @@ namespace Harness {
|
||||
};
|
||||
}
|
||||
|
||||
interface URL {
|
||||
hash: string;
|
||||
host: string;
|
||||
hostname: string;
|
||||
href: string;
|
||||
password: string;
|
||||
pathname: string;
|
||||
port: string;
|
||||
protocol: string;
|
||||
search: string;
|
||||
username: string;
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
declare var URL: {
|
||||
prototype: URL;
|
||||
new(url: string, base?: string | URL): URL;
|
||||
};
|
||||
|
||||
function createBrowserIO(): IO {
|
||||
const serverRoot = new URL("http://localhost:8888/");
|
||||
|
||||
class HttpHeaders extends collections.SortedMap<string, string | string[]> {
|
||||
constructor(template?: Record<string, string | string[]>) {
|
||||
super(ts.compareStringsCaseInsensitive);
|
||||
if (template) {
|
||||
for (const key in template) {
|
||||
if (ts.hasProperty(template, key)) {
|
||||
this.set(key, template[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static combine(left: HttpHeaders | undefined, right: HttpHeaders | undefined): HttpHeaders | undefined {
|
||||
if (!left && !right) return undefined;
|
||||
const headers = new HttpHeaders();
|
||||
if (left) left.forEach((value, key) => { headers.set(key, value); });
|
||||
if (right) right.forEach((value, key) => { headers.set(key, value); });
|
||||
return headers;
|
||||
}
|
||||
|
||||
public has(key: string) {
|
||||
return super.has(key.toLowerCase());
|
||||
}
|
||||
|
||||
public get(key: string) {
|
||||
return super.get(key.toLowerCase());
|
||||
}
|
||||
|
||||
public set(key: string, value: string | string[]) {
|
||||
return super.set(key.toLowerCase(), value);
|
||||
}
|
||||
|
||||
public delete(key: string) {
|
||||
return super.delete(key.toLowerCase());
|
||||
}
|
||||
|
||||
public writeRequestHeaders(xhr: XMLHttpRequest) {
|
||||
this.forEach((values, key) => {
|
||||
if (key === "access-control-allow-origin" || key === "content-length") return;
|
||||
const value = Array.isArray(values) ? values.join(",") : values;
|
||||
if (key === "content-type") {
|
||||
xhr.overrideMimeType(value);
|
||||
return;
|
||||
}
|
||||
xhr.setRequestHeader(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
public static readResponseHeaders(xhr: XMLHttpRequest): HttpHeaders {
|
||||
const allHeaders = xhr.getAllResponseHeaders();
|
||||
const headers = new HttpHeaders();
|
||||
for (const header of allHeaders.split(/\r\n/g)) {
|
||||
const colonIndex = header.indexOf(":");
|
||||
if (colonIndex >= 0) {
|
||||
const key = header.slice(0, colonIndex).trim();
|
||||
const value = header.slice(colonIndex + 1).trim();
|
||||
const values = value.split(",");
|
||||
headers.set(key, values.length > 1 ? values : value);
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
|
||||
class HttpContent {
|
||||
public headers: HttpHeaders;
|
||||
public content: string;
|
||||
|
||||
constructor(headers: HttpHeaders | Record<string, string | string[]>, content: string) {
|
||||
this.headers = headers instanceof HttpHeaders ? headers : new HttpHeaders(headers);
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public static fromMediaType(mediaType: string, content: string) {
|
||||
return new HttpContent({ "Content-Type": mediaType }, content);
|
||||
}
|
||||
|
||||
public static text(content: string) {
|
||||
return HttpContent.fromMediaType("text/plain", content);
|
||||
}
|
||||
|
||||
public static json(content: object) {
|
||||
return HttpContent.fromMediaType("application/json", JSON.stringify(content));
|
||||
}
|
||||
|
||||
public static readResponseContent(xhr: XMLHttpRequest) {
|
||||
if (typeof xhr.responseText === "string") {
|
||||
return new HttpContent({
|
||||
"Content-Type": xhr.getResponseHeader("Content-Type") || undefined!, // TODO: GH#18217
|
||||
"Content-Length": xhr.getResponseHeader("Content-Length") || undefined!, // TODO: GH#18217
|
||||
}, xhr.responseText);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public writeRequestHeaders(xhr: XMLHttpRequest) {
|
||||
this.headers.writeRequestHeaders(xhr);
|
||||
}
|
||||
}
|
||||
|
||||
class HttpRequestMessage {
|
||||
public method: string;
|
||||
public url: URL;
|
||||
public headers: HttpHeaders;
|
||||
public content?: HttpContent;
|
||||
|
||||
constructor(method: string, url: string | URL, headers?: HttpHeaders | Record<string, string | string[]>, content?: HttpContent) {
|
||||
this.method = method;
|
||||
this.url = typeof url === "string" ? new URL(url) : url;
|
||||
this.headers = headers instanceof HttpHeaders ? headers : new HttpHeaders(headers);
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public static options(url: string | URL) {
|
||||
return new HttpRequestMessage("OPTIONS", url);
|
||||
}
|
||||
|
||||
public static head(url: string | URL) {
|
||||
return new HttpRequestMessage("HEAD", url);
|
||||
}
|
||||
|
||||
public static get(url: string | URL) {
|
||||
return new HttpRequestMessage("GET", url);
|
||||
}
|
||||
|
||||
public static delete(url: string | URL) {
|
||||
return new HttpRequestMessage("DELETE", url);
|
||||
}
|
||||
|
||||
public static put(url: string | URL, content: HttpContent) {
|
||||
return new HttpRequestMessage("PUT", url, /*headers*/ undefined, content);
|
||||
}
|
||||
|
||||
public static post(url: string | URL, content: HttpContent) {
|
||||
return new HttpRequestMessage("POST", url, /*headers*/ undefined, content);
|
||||
}
|
||||
|
||||
public writeRequestHeaders(xhr: XMLHttpRequest) {
|
||||
this.headers.writeRequestHeaders(xhr);
|
||||
if (this.content) {
|
||||
this.content.writeRequestHeaders(xhr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HttpResponseMessage {
|
||||
public statusCode: number;
|
||||
public statusMessage: string;
|
||||
public headers: HttpHeaders;
|
||||
public content?: HttpContent;
|
||||
|
||||
constructor(statusCode: number, statusMessage: string, headers?: HttpHeaders | Record<string, string | string[]>, content?: HttpContent) {
|
||||
this.statusCode = statusCode;
|
||||
this.statusMessage = statusMessage;
|
||||
this.headers = headers instanceof HttpHeaders ? headers : new HttpHeaders(headers);
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public static notFound(): HttpResponseMessage {
|
||||
return new HttpResponseMessage(404, "Not Found");
|
||||
}
|
||||
|
||||
public static hasSuccessStatusCode(response: HttpResponseMessage) {
|
||||
return response.statusCode === 304 || (response.statusCode >= 200 && response.statusCode < 300);
|
||||
}
|
||||
|
||||
public static readResponseMessage(xhr: XMLHttpRequest) {
|
||||
return new HttpResponseMessage(
|
||||
xhr.status,
|
||||
xhr.statusText,
|
||||
HttpHeaders.readResponseHeaders(xhr),
|
||||
HttpContent.readResponseContent(xhr));
|
||||
}
|
||||
}
|
||||
|
||||
function send(request: HttpRequestMessage): HttpResponseMessage {
|
||||
const xhr = new XMLHttpRequest();
|
||||
try {
|
||||
xhr.open(request.method, request.url.toString(), /*async*/ false);
|
||||
request.writeRequestHeaders(xhr);
|
||||
xhr.setRequestHeader("Access-Control-Allow-Origin", "*");
|
||||
xhr.send(request.content && request.content.content);
|
||||
while (xhr.readyState !== 4); // block until ready
|
||||
return HttpResponseMessage.readResponseMessage(xhr);
|
||||
}
|
||||
catch (e) {
|
||||
return HttpResponseMessage.notFound();
|
||||
}
|
||||
}
|
||||
|
||||
let caseSensitivity: "CI" | "CS" | undefined;
|
||||
|
||||
function useCaseSensitiveFileNames() {
|
||||
if (!caseSensitivity) {
|
||||
const response = send(HttpRequestMessage.options(new URL("*", serverRoot)));
|
||||
const xCaseSensitivity = response.headers.get("X-Case-Sensitivity");
|
||||
caseSensitivity = xCaseSensitivity === "CS" ? "CS" : "CI";
|
||||
}
|
||||
return caseSensitivity === "CS";
|
||||
}
|
||||
|
||||
function resolvePath(path: string) {
|
||||
const response = send(HttpRequestMessage.post(new URL("/api/resolve", serverRoot), HttpContent.text(path)));
|
||||
return HttpResponseMessage.hasSuccessStatusCode(response) && response.content ? response.content.content : undefined;
|
||||
}
|
||||
|
||||
function getFileSize(path: string): number {
|
||||
const response = send(HttpRequestMessage.head(new URL(path, serverRoot)));
|
||||
return HttpResponseMessage.hasSuccessStatusCode(response) ? +response.headers.get("Content-Length")!.toString() : 0;
|
||||
}
|
||||
|
||||
function readFile(path: string): string | undefined {
|
||||
const response = send(HttpRequestMessage.get(new URL(path, serverRoot)));
|
||||
return HttpResponseMessage.hasSuccessStatusCode(response) && response.content ? response.content.content : undefined;
|
||||
}
|
||||
|
||||
function writeFile(path: string, contents: string) {
|
||||
send(HttpRequestMessage.put(new URL(path, serverRoot), HttpContent.text(contents)));
|
||||
}
|
||||
|
||||
function fileExists(path: string): boolean {
|
||||
const response = send(HttpRequestMessage.head(new URL(path, serverRoot)));
|
||||
return HttpResponseMessage.hasSuccessStatusCode(response);
|
||||
}
|
||||
|
||||
function directoryExists(path: string): boolean {
|
||||
const response = send(HttpRequestMessage.post(new URL("/api/directoryExists", serverRoot), HttpContent.text(path)));
|
||||
return hasJsonContent(response) && JSON.parse(response.content.content) as boolean;
|
||||
}
|
||||
|
||||
function deleteFile(path: string) {
|
||||
send(HttpRequestMessage.delete(new URL(path, serverRoot)));
|
||||
}
|
||||
|
||||
function directoryName(path: string) {
|
||||
const url = new URL(path, serverRoot);
|
||||
return ts.getDirectoryPath(ts.normalizeSlashes(url.pathname || "/"));
|
||||
}
|
||||
|
||||
function enumerateTestFiles(runner: RunnerBase): (string | FileBasedTest)[] {
|
||||
const response = send(HttpRequestMessage.post(new URL("/api/enumerateTestFiles", serverRoot), HttpContent.text(runner.kind())));
|
||||
return hasJsonContent(response) ? JSON.parse(response.content.content) : [];
|
||||
}
|
||||
|
||||
function listFiles(dirname: string, spec?: RegExp, options?: { recursive?: boolean }): string[] {
|
||||
if (spec || (options && !options.recursive)) {
|
||||
let results = IO.listFiles(dirname);
|
||||
if (spec) {
|
||||
results = results.filter(file => spec.test(file));
|
||||
}
|
||||
if (options && !options.recursive) {
|
||||
results = results.filter(file => ts.getDirectoryPath(ts.normalizeSlashes(file)) === dirname);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
const response = send(HttpRequestMessage.post(new URL("/api/listFiles", serverRoot), HttpContent.text(dirname)));
|
||||
return hasJsonContent(response) ? JSON.parse(response.content.content) : [];
|
||||
}
|
||||
|
||||
function readDirectory(path: string, extension?: string[], exclude?: string[], include?: string[], depth?: number) {
|
||||
return ts.matchFiles(path, extension, exclude, include, useCaseSensitiveFileNames(), "", depth, getAccessibleFileSystemEntries);
|
||||
}
|
||||
|
||||
function getAccessibleFileSystemEntries(dirname: string): ts.FileSystemEntries {
|
||||
const response = send(HttpRequestMessage.post(new URL("/api/getAccessibleFileSystemEntries", serverRoot), HttpContent.text(dirname)));
|
||||
return hasJsonContent(response) ? JSON.parse(response.content.content) : { files: [], directories: [] };
|
||||
}
|
||||
|
||||
function hasJsonContent(response: HttpResponseMessage): response is HttpResponseMessage & { content: HttpContent } {
|
||||
return HttpResponseMessage.hasSuccessStatusCode(response)
|
||||
&& !!response.content
|
||||
&& /^application\/json(;.*)$/.test("" + response.content.headers.get("Content-Type"));
|
||||
}
|
||||
|
||||
return {
|
||||
newLine: () => harnessNewLine,
|
||||
getCurrentDirectory: () => "",
|
||||
useCaseSensitiveFileNames,
|
||||
resolvePath,
|
||||
getFileSize,
|
||||
readFile,
|
||||
writeFile,
|
||||
directoryName: Utils.memoize(directoryName, path => path),
|
||||
getDirectories: () => [],
|
||||
createDirectory: () => {}, // tslint:disable-line no-empty
|
||||
fileExists,
|
||||
directoryExists,
|
||||
deleteFile,
|
||||
listFiles: Utils.memoize(listFiles, (path, spec, options) => `${path}|${spec}|${options ? options.recursive === true : true}`),
|
||||
enumerateTestFiles: Utils.memoize(enumerateTestFiles, runner => runner.kind()),
|
||||
log: s => console.log(s),
|
||||
args: () => [],
|
||||
getExecutingFilePath: () => "",
|
||||
exit: () => {}, // tslint:disable-line no-empty
|
||||
readDirectory,
|
||||
getAccessibleFileSystemEntries,
|
||||
getWorkspaceRoot: () => "/"
|
||||
};
|
||||
}
|
||||
|
||||
export function mockHash(s: string): string {
|
||||
return `hash-${s}`;
|
||||
}
|
||||
|
||||
const environment = Utils.getExecutionEnvironment();
|
||||
switch (environment) {
|
||||
case Utils.ExecutionEnvironment.Node:
|
||||
IO = createNodeIO();
|
||||
break;
|
||||
case Utils.ExecutionEnvironment.Browser:
|
||||
IO = createBrowserIO();
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown value '${environment}' for ExecutionEnvironment.`);
|
||||
}
|
||||
IO = createNodeIO();
|
||||
}
|
||||
|
||||
if (Harness.IO.tryEnableSourceMapsForHost && /^development$/i.test(Harness.IO.getEnvironmentVariable!("NODE_ENV"))) {
|
||||
@@ -970,10 +609,8 @@ if (Harness.IO.tryEnableSourceMapsForHost && /^development$/i.test(Harness.IO.ge
|
||||
|
||||
namespace Harness {
|
||||
export const libFolder = "built/local/";
|
||||
const tcServicesFileName = ts.combinePaths(libFolder, Utils.getExecutionEnvironment() === Utils.ExecutionEnvironment.Browser ? "typescriptServicesInBrowserTest.js" : "typescriptServices.js");
|
||||
export const tcServicesFile = IO.readFile(tcServicesFileName) + (Utils.getExecutionEnvironment() !== Utils.ExecutionEnvironment.Browser
|
||||
? IO.newLine() + `//# sourceURL=${IO.resolvePath(tcServicesFileName)}`
|
||||
: "");
|
||||
const tcServicesFileName = ts.combinePaths(libFolder, "typescriptServices.js");
|
||||
export const tcServicesFile = IO.readFile(tcServicesFileName) + IO.newLine() + `//# sourceURL=${IO.resolvePath(tcServicesFileName)}`;
|
||||
|
||||
export type SourceMapEmitterCallback = (
|
||||
emittedFile: string,
|
||||
|
||||
+103
-35
@@ -408,6 +408,21 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export interface OpenFileArguments {
|
||||
fileName: string;
|
||||
content?: string;
|
||||
scriptKind?: protocol.ScriptKindName | ScriptKind;
|
||||
hasMixedContent?: boolean;
|
||||
projectRootPath?: string;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export interface ChangeFileArguments {
|
||||
fileName: string;
|
||||
changes: Iterator<TextChange>;
|
||||
}
|
||||
|
||||
export class ProjectService {
|
||||
|
||||
/*@internal*/
|
||||
@@ -988,7 +1003,7 @@ namespace ts.server {
|
||||
fileOrDirectory => {
|
||||
const fileOrDirectoryPath = this.toPath(fileOrDirectory);
|
||||
project.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
|
||||
if (isPathInNodeModulesStartingWithDot(fileOrDirectoryPath)) return;
|
||||
if (isPathIgnored(fileOrDirectoryPath)) return;
|
||||
const configFilename = project.getConfigFilePath();
|
||||
|
||||
// If the the added or created file or directory is not supported file name, ignore the file
|
||||
@@ -1129,11 +1144,22 @@ namespace ts.server {
|
||||
return project;
|
||||
}
|
||||
|
||||
private assignOrphanScriptInfosToInferredProject() {
|
||||
// collect orphaned files and assign them to inferred project just like we treat open of a file
|
||||
this.openFiles.forEach((projectRootPath, path) => {
|
||||
const info = this.getScriptInfoForPath(path as Path)!;
|
||||
// collect all orphaned script infos from open files
|
||||
if (info.isOrphan()) {
|
||||
this.assignOrphanScriptInfoToInferredProject(info, projectRootPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this file from the set of open, non-configured files.
|
||||
* @param info The file that has been closed or newly configured
|
||||
*/
|
||||
private closeOpenFile(info: ScriptInfo): void {
|
||||
private closeOpenFile(info: ScriptInfo, skipAssignOrphanScriptInfosToInferredProject?: true) {
|
||||
// Closing file should trigger re-reading the file content from disk. This is
|
||||
// because the user may chose to discard the buffer content before saving
|
||||
// to the disk, and the server's version of the file can be out of sync.
|
||||
@@ -1177,15 +1203,8 @@ namespace ts.server {
|
||||
|
||||
this.openFiles.delete(info.path);
|
||||
|
||||
if (ensureProjectsForOpenFiles) {
|
||||
// collect orphaned files and assign them to inferred project just like we treat open of a file
|
||||
this.openFiles.forEach((projectRootPath, path) => {
|
||||
const info = this.getScriptInfoForPath(path as Path)!;
|
||||
// collect all orphaned script infos from open files
|
||||
if (info.isOrphan()) {
|
||||
this.assignOrphanScriptInfoToInferredProject(info, projectRootPath);
|
||||
}
|
||||
});
|
||||
if (!skipAssignOrphanScriptInfosToInferredProject && ensureProjectsForOpenFiles) {
|
||||
this.assignOrphanScriptInfosToInferredProject();
|
||||
}
|
||||
|
||||
// Cleanup script infos that arent part of any project (eg. those could be closed script infos not referenced by any project)
|
||||
@@ -1200,6 +1219,8 @@ namespace ts.server {
|
||||
else {
|
||||
this.handleDeletedFile(info);
|
||||
}
|
||||
|
||||
return ensureProjectsForOpenFiles;
|
||||
}
|
||||
|
||||
private deleteScriptInfo(info: ScriptInfo) {
|
||||
@@ -2051,7 +2072,7 @@ namespace ts.server {
|
||||
watchDir,
|
||||
(fileOrDirectory) => {
|
||||
const fileOrDirectoryPath = this.toPath(fileOrDirectory);
|
||||
if (isPathInNodeModulesStartingWithDot(fileOrDirectoryPath)) return;
|
||||
if (isPathIgnored(fileOrDirectoryPath)) return;
|
||||
|
||||
// Has extension
|
||||
Debug.assert(result.refCount > 0);
|
||||
@@ -2571,20 +2592,22 @@ namespace ts.server {
|
||||
});
|
||||
}
|
||||
|
||||
openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult {
|
||||
private getOrCreateOpenScriptInfo(fileName: NormalizedPath, fileContent: string | undefined, scriptKind: ScriptKind | undefined, hasMixedContent: boolean | undefined, projectRootPath: NormalizedPath | undefined) {
|
||||
const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, projectRootPath ? this.getNormalizedAbsolutePath(projectRootPath) : this.currentDirectory, fileContent, scriptKind, hasMixedContent)!; // TODO: GH#18217
|
||||
this.openFiles.set(info.path, projectRootPath);
|
||||
return info;
|
||||
}
|
||||
|
||||
private assignProjectToOpenedScriptInfo(info: ScriptInfo): OpenConfiguredProjectResult {
|
||||
let configFileName: NormalizedPath | undefined;
|
||||
let configFileErrors: ReadonlyArray<Diagnostic> | undefined;
|
||||
|
||||
const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, projectRootPath ? this.getNormalizedAbsolutePath(projectRootPath) : this.currentDirectory, fileContent, scriptKind, hasMixedContent)!; // TODO: GH#18217
|
||||
|
||||
this.openFiles.set(info.path, projectRootPath);
|
||||
let project: ConfiguredProject | ExternalProject | undefined = this.findExternalProjectContainingOpenScriptInfo(info);
|
||||
if (!project && !this.syntaxOnly) { // Checking syntaxOnly is an optimization
|
||||
configFileName = this.getConfigFileNameForFile(info);
|
||||
if (configFileName) {
|
||||
project = this.findConfiguredProjectByProjectName(configFileName);
|
||||
if (!project) {
|
||||
project = this.createLoadAndUpdateConfiguredProject(configFileName, `Creating possible configured project for ${fileName} to open`);
|
||||
project = this.createLoadAndUpdateConfiguredProject(configFileName, `Creating possible configured project for ${info.fileName} to open`);
|
||||
// Send the event only if the project got created as part of this open request and info is part of the project
|
||||
if (info.isOrphan()) {
|
||||
// Since the file isnt part of configured project, do not send config file info
|
||||
@@ -2592,7 +2615,7 @@ namespace ts.server {
|
||||
}
|
||||
else {
|
||||
configFileErrors = project.getAllProjectErrors();
|
||||
this.sendConfigFileDiagEvent(project, fileName);
|
||||
this.sendConfigFileDiagEvent(project, info.fileName);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -2614,10 +2637,14 @@ namespace ts.server {
|
||||
// At this point if file is part of any any configured or external project, then it would be present in the containing projects
|
||||
// So if it still doesnt have any containing projects, it needs to be part of inferred project
|
||||
if (info.isOrphan()) {
|
||||
this.assignOrphanScriptInfoToInferredProject(info, projectRootPath);
|
||||
Debug.assert(this.openFiles.has(info.path));
|
||||
this.assignOrphanScriptInfoToInferredProject(info, this.openFiles.get(info.path));
|
||||
}
|
||||
Debug.assert(!info.isOrphan());
|
||||
return { configFileName, configFileErrors };
|
||||
}
|
||||
|
||||
private cleanupAfterOpeningFile() {
|
||||
// This was postponed from closeOpenFile to after opening next file,
|
||||
// so that we can reuse the project if we need to right away
|
||||
this.removeOrphanConfiguredProjects();
|
||||
@@ -2637,9 +2664,14 @@ namespace ts.server {
|
||||
this.removeOrphanScriptInfos();
|
||||
|
||||
this.printProjects();
|
||||
}
|
||||
|
||||
openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult {
|
||||
const info = this.getOrCreateOpenScriptInfo(fileName, fileContent, scriptKind, hasMixedContent, projectRootPath);
|
||||
const result = this.assignProjectToOpenedScriptInfo(info);
|
||||
this.cleanupAfterOpeningFile();
|
||||
this.telemetryOnOpenFile(info);
|
||||
return { configFileName, configFileErrors };
|
||||
return result;
|
||||
}
|
||||
|
||||
private removeOrphanConfiguredProjects() {
|
||||
@@ -2746,12 +2778,16 @@ namespace ts.server {
|
||||
* Close file whose contents is managed by the client
|
||||
* @param filename is absolute pathname
|
||||
*/
|
||||
closeClientFile(uncheckedFileName: string) {
|
||||
closeClientFile(uncheckedFileName: string): void;
|
||||
/*@internal*/
|
||||
closeClientFile(uncheckedFileName: string, skipAssignOrphanScriptInfosToInferredProject: true): boolean;
|
||||
closeClientFile(uncheckedFileName: string, skipAssignOrphanScriptInfosToInferredProject?: true) {
|
||||
const info = this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName));
|
||||
if (info) {
|
||||
this.closeOpenFile(info);
|
||||
const result = info ? this.closeOpenFile(info, skipAssignOrphanScriptInfosToInferredProject) : false;
|
||||
if (!skipAssignOrphanScriptInfosToInferredProject) {
|
||||
this.printProjects();
|
||||
}
|
||||
this.printProjects();
|
||||
return result;
|
||||
}
|
||||
|
||||
private collectChanges(lastKnownProjectVersions: protocol.ProjectVersionInfo[], currentProjects: Project[], result: ProjectFilesWithTSDiagnostics[]): void {
|
||||
@@ -2771,36 +2807,68 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
applyChangesInOpenFiles(openFiles: protocol.ExternalFile[] | undefined, changedFiles: protocol.ChangedOpenFile[] | undefined, closedFiles: string[] | undefined): void {
|
||||
applyChangesInOpenFiles(openFiles: Iterator<OpenFileArguments> | undefined, changedFiles?: Iterator<ChangeFileArguments>, closedFiles?: string[]): void {
|
||||
let openScriptInfos: ScriptInfo[] | undefined;
|
||||
let assignOrphanScriptInfosToInferredProject = false;
|
||||
if (openFiles) {
|
||||
for (const file of openFiles) {
|
||||
while (true) {
|
||||
const { value: file, done } = openFiles.next();
|
||||
if (done) break;
|
||||
const scriptInfo = this.getScriptInfo(file.fileName);
|
||||
Debug.assert(!scriptInfo || !scriptInfo.isScriptOpen(), "Script should not exist and not be open already");
|
||||
const normalizedPath = scriptInfo ? scriptInfo.fileName : toNormalizedPath(file.fileName);
|
||||
this.openClientFileWithNormalizedPath(normalizedPath, file.content, tryConvertScriptKindName(file.scriptKind!), file.hasMixedContent); // TODO: GH#18217
|
||||
// Create script infos so we have the new content for all the open files before we do any updates to projects
|
||||
const info = this.getOrCreateOpenScriptInfo(
|
||||
scriptInfo ? scriptInfo.fileName : toNormalizedPath(file.fileName),
|
||||
file.content,
|
||||
tryConvertScriptKindName(file.scriptKind!),
|
||||
file.hasMixedContent,
|
||||
file.projectRootPath ? toNormalizedPath(file.projectRootPath) : undefined
|
||||
);
|
||||
(openScriptInfos || (openScriptInfos = [])).push(info);
|
||||
}
|
||||
}
|
||||
|
||||
if (changedFiles) {
|
||||
for (const file of changedFiles) {
|
||||
while (true) {
|
||||
const { value: file, done } = changedFiles.next();
|
||||
if (done) break;
|
||||
const scriptInfo = this.getScriptInfo(file.fileName)!;
|
||||
Debug.assert(!!scriptInfo);
|
||||
// Make edits to script infos and marks containing project as dirty
|
||||
this.applyChangesToFile(scriptInfo, file.changes);
|
||||
}
|
||||
}
|
||||
|
||||
if (closedFiles) {
|
||||
for (const file of closedFiles) {
|
||||
this.closeClientFile(file);
|
||||
// Close files, but dont assign projects to orphan open script infos, that part comes later
|
||||
assignOrphanScriptInfosToInferredProject = this.closeClientFile(file, /*skipAssignOrphanScriptInfosToInferredProject*/ true) || assignOrphanScriptInfosToInferredProject;
|
||||
}
|
||||
}
|
||||
|
||||
// All the script infos now exist, so ok to go update projects for open files
|
||||
if (openScriptInfos) {
|
||||
openScriptInfos.forEach(info => this.assignProjectToOpenedScriptInfo(info));
|
||||
}
|
||||
|
||||
// While closing files there could be open files that needed assigning new inferred projects, do it now
|
||||
if (assignOrphanScriptInfosToInferredProject) {
|
||||
this.assignOrphanScriptInfosToInferredProject();
|
||||
}
|
||||
|
||||
// Cleanup projects
|
||||
this.cleanupAfterOpeningFile();
|
||||
|
||||
// Telemetry
|
||||
forEach(openScriptInfos, info => this.telemetryOnOpenFile(info));
|
||||
this.printProjects();
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
applyChangesToFile(scriptInfo: ScriptInfo, changes: TextChange[]) {
|
||||
// apply changes in reverse order
|
||||
for (let i = changes.length - 1; i >= 0; i--) {
|
||||
const change = changes[i];
|
||||
applyChangesToFile(scriptInfo: ScriptInfo, changes: Iterator<TextChange>) {
|
||||
while (true) {
|
||||
const { value: change, done } = changes.next();
|
||||
if (done) break;
|
||||
scriptInfo.editContent(change.span.start, change.span.start + change.span.length, change.newText);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,7 @@ namespace ts.server.protocol {
|
||||
SynchronizeProjectList = "synchronizeProjectList",
|
||||
/* @internal */
|
||||
ApplyChangedToOpenFiles = "applyChangedToOpenFiles",
|
||||
UpdateOpen = "updateOpen",
|
||||
/* @internal */
|
||||
EncodedSemanticClassificationsFull = "encodedSemanticClassifications-full",
|
||||
/* @internal */
|
||||
@@ -1543,6 +1544,32 @@ namespace ts.server.protocol {
|
||||
closedFiles?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Request to synchronize list of open files with the client
|
||||
*/
|
||||
export interface UpdateOpenRequest extends Request {
|
||||
command: CommandTypes.UpdateOpen;
|
||||
arguments: UpdateOpenRequestArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arguments to UpdateOpenRequest
|
||||
*/
|
||||
export interface UpdateOpenRequestArgs {
|
||||
/**
|
||||
* List of newly open files
|
||||
*/
|
||||
openFiles?: OpenRequestArgs[];
|
||||
/**
|
||||
* List of open files files that were changes
|
||||
*/
|
||||
changedFiles?: FileCodeEdits[];
|
||||
/**
|
||||
* List of files that were closed
|
||||
*/
|
||||
closedFiles?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Request to set compiler options for inferred projects.
|
||||
* External projects are opened / closed explicitly.
|
||||
|
||||
+33
-3
@@ -1654,10 +1654,10 @@ namespace ts.server {
|
||||
const end = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset);
|
||||
if (start >= 0) {
|
||||
this.changeSeq++;
|
||||
this.projectService.applyChangesToFile(scriptInfo, [{
|
||||
this.projectService.applyChangesToFile(scriptInfo, singleIterator({
|
||||
span: { start, length: end - start },
|
||||
newText: args.insertString! // TODO: GH#18217
|
||||
}]);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2096,9 +2096,39 @@ namespace ts.server {
|
||||
});
|
||||
return this.requiredResponse(converted);
|
||||
},
|
||||
[CommandNames.UpdateOpen]: (request: protocol.UpdateOpenRequest) => {
|
||||
this.changeSeq++;
|
||||
this.projectService.applyChangesInOpenFiles(
|
||||
request.arguments.openFiles && mapIterator(arrayIterator(request.arguments.openFiles), file => ({
|
||||
fileName: file.file,
|
||||
content: file.fileContent,
|
||||
scriptKind: file.scriptKindName,
|
||||
projectRootPath: file.projectRootPath
|
||||
})),
|
||||
request.arguments.changedFiles && mapIterator(arrayIterator(request.arguments.changedFiles), file => ({
|
||||
fileName: file.fileName,
|
||||
changes: mapDefinedIterator(arrayReverseIterator(file.textChanges), change => {
|
||||
const scriptInfo = Debug.assertDefined(this.projectService.getScriptInfo(file.fileName));
|
||||
const start = scriptInfo.lineOffsetToPosition(change.start.line, change.start.offset);
|
||||
const end = scriptInfo.lineOffsetToPosition(change.end.line, change.end.offset);
|
||||
return start >= 0 ? { span: { start, length: end - start }, newText: change.newText } : undefined;
|
||||
})
|
||||
})),
|
||||
request.arguments.closedFiles
|
||||
);
|
||||
return this.requiredResponse(/*response*/ true);
|
||||
},
|
||||
[CommandNames.ApplyChangedToOpenFiles]: (request: protocol.ApplyChangedToOpenFilesRequest) => {
|
||||
this.changeSeq++;
|
||||
this.projectService.applyChangesInOpenFiles(request.arguments.openFiles, request.arguments.changedFiles!, request.arguments.closedFiles!); // TODO: GH#18217
|
||||
this.projectService.applyChangesInOpenFiles(
|
||||
request.arguments.openFiles && arrayIterator(request.arguments.openFiles),
|
||||
request.arguments.changedFiles && mapIterator(arrayIterator(request.arguments.changedFiles), file => ({
|
||||
fileName: file.fileName,
|
||||
// apply changes in reverse order
|
||||
changes: arrayReverseIterator(file.changes)
|
||||
})),
|
||||
request.arguments.closedFiles
|
||||
);
|
||||
// TODO: report errors
|
||||
return this.requiredResponse(/*response*/ true);
|
||||
},
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace ts.codefix {
|
||||
precedingNode = ctorDeclaration.parent.parent;
|
||||
newClassDeclaration = createClassFromVariableDeclaration(ctorDeclaration as VariableDeclaration);
|
||||
if ((<VariableDeclarationList>ctorDeclaration.parent).declarations.length === 1) {
|
||||
copyComments(precedingNode, newClassDeclaration!, sourceFile); // TODO: GH#18217
|
||||
copyLeadingComments(precedingNode, newClassDeclaration!, sourceFile); // TODO: GH#18217
|
||||
changes.delete(sourceFile, precedingNode);
|
||||
}
|
||||
else {
|
||||
@@ -48,7 +48,7 @@ namespace ts.codefix {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
copyComments(ctorDeclaration, newClassDeclaration, sourceFile);
|
||||
copyLeadingComments(ctorDeclaration, newClassDeclaration, sourceFile);
|
||||
|
||||
// Because the preceding node could be touched, we need to insert nodes before delete nodes.
|
||||
changes.insertNodeAfter(sourceFile, precedingNode!, newClassDeclaration);
|
||||
@@ -112,7 +112,7 @@ namespace ts.codefix {
|
||||
const fullModifiers = concatenate(modifiers, getModifierKindFromSource(functionExpression, SyntaxKind.AsyncKeyword));
|
||||
const method = createMethod(/*decorators*/ undefined, fullModifiers, /*asteriskToken*/ undefined, memberDeclaration.name, /*questionToken*/ undefined,
|
||||
/*typeParameters*/ undefined, functionExpression.parameters, /*type*/ undefined, functionExpression.body);
|
||||
copyComments(assignmentBinaryExpression, method, sourceFile);
|
||||
copyLeadingComments(assignmentBinaryExpression, method, sourceFile);
|
||||
return method;
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace ts.codefix {
|
||||
const fullModifiers = concatenate(modifiers, getModifierKindFromSource(arrowFunction, SyntaxKind.AsyncKeyword));
|
||||
const method = createMethod(/*decorators*/ undefined, fullModifiers, /*asteriskToken*/ undefined, memberDeclaration.name, /*questionToken*/ undefined,
|
||||
/*typeParameters*/ undefined, arrowFunction.parameters, /*type*/ undefined, bodyBlock);
|
||||
copyComments(assignmentBinaryExpression, method, sourceFile);
|
||||
copyLeadingComments(assignmentBinaryExpression, method, sourceFile);
|
||||
return method;
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace ts.codefix {
|
||||
}
|
||||
const prop = createProperty(/*decorators*/ undefined, modifiers, memberDeclaration.name, /*questionToken*/ undefined,
|
||||
/*type*/ undefined, assignmentBinaryExpression.right);
|
||||
copyComments(assignmentBinaryExpression.parent, prop, sourceFile);
|
||||
copyLeadingComments(assignmentBinaryExpression.parent, prop, sourceFile);
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,30 +304,6 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined {
|
||||
const checker = program.getTypeChecker();
|
||||
let typeIsAccessible = true;
|
||||
const notAccessible = () => { typeIsAccessible = false; };
|
||||
const res = checker.typeToTypeNode(type, enclosingScope, /*flags*/ undefined, {
|
||||
trackSymbol: (symbol, declaration, meaning) => {
|
||||
// TODO: GH#18217
|
||||
typeIsAccessible = typeIsAccessible && checker.isSymbolAccessible(symbol, declaration, meaning!, /*shouldComputeAliasToMarkVisible*/ false).accessibility === SymbolAccessibility.Accessible;
|
||||
},
|
||||
reportInaccessibleThisError: notAccessible,
|
||||
reportPrivateInBaseOfClassExpression: notAccessible,
|
||||
reportInaccessibleUniqueSymbolError: notAccessible,
|
||||
moduleResolverHost: {
|
||||
readFile: host.readFile,
|
||||
fileExists: host.fileExists,
|
||||
directoryExists: host.directoryExists,
|
||||
getSourceFiles: program.getSourceFiles,
|
||||
getCurrentDirectory: program.getCurrentDirectory,
|
||||
getCommonSourceDirectory: program.getCommonSourceDirectory,
|
||||
}
|
||||
});
|
||||
return typeIsAccessible ? res : undefined;
|
||||
}
|
||||
|
||||
function getReferences(token: PropertyName | Token<SyntaxKind.ConstructorKeyword>, program: Program, cancellationToken: CancellationToken): ReadonlyArray<Identifier> {
|
||||
// Position shouldn't matter since token is not a SourceFile.
|
||||
return mapDefined(FindAllReferences.getReferenceEntriesForNode(-1, token, program, program.getSourceFiles(), cancellationToken), entry =>
|
||||
|
||||
@@ -68,8 +68,8 @@ namespace ts.OrganizeImports {
|
||||
else {
|
||||
// Note: Delete the surrounding trivia because it will have been retained in newImportDecls.
|
||||
changeTracker.replaceNodeWithNodes(sourceFile, oldImportDecls[0], newImportDecls, {
|
||||
useNonAdjustedStartPosition: true, // Leave header comment in place
|
||||
useNonAdjustedEndPosition: false,
|
||||
leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, // Leave header comment in place
|
||||
trailingTriviaOption: textChanges.TrailingTriviaOption.Include,
|
||||
suffix: getNewLineOrDefaultFromHost(host, formatContext.options),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,13 +48,13 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction {
|
||||
const returnStatement = createReturn(expression);
|
||||
body = createBlock([returnStatement], /* multiLine */ true);
|
||||
suppressLeadingAndTrailingTrivia(body);
|
||||
copyComments(expression!, returnStatement, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ true);
|
||||
copyLeadingComments(expression!, returnStatement, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ true);
|
||||
}
|
||||
else if (actionName === removeBracesActionName && returnStatement) {
|
||||
const actualExpression = expression || createVoidZero();
|
||||
body = needsParentheses(actualExpression) ? createParen(actualExpression) : actualExpression;
|
||||
suppressLeadingAndTrailingTrivia(body);
|
||||
copyComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false);
|
||||
copyLeadingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false);
|
||||
}
|
||||
else {
|
||||
Debug.fail("invalid action");
|
||||
|
||||
@@ -0,0 +1,520 @@
|
||||
/* @internal */
|
||||
namespace ts.refactor.convertToNamedParameters {
|
||||
const refactorName = "Convert to named parameters";
|
||||
const minimumParameterLength = 2;
|
||||
registerRefactor(refactorName, { getEditsForAction, getAvailableActions });
|
||||
|
||||
|
||||
function getAvailableActions(context: RefactorContext): ReadonlyArray<ApplicableRefactorInfo> {
|
||||
const { file, startPosition } = context;
|
||||
const isJSFile = isSourceFileJS(file);
|
||||
if (isJSFile) return emptyArray; // TODO: GH#30113
|
||||
const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, context.program.getTypeChecker());
|
||||
if (!functionDeclaration) return emptyArray;
|
||||
|
||||
const description = getLocaleSpecificMessage(Diagnostics.Convert_to_named_parameters);
|
||||
return [{
|
||||
name: refactorName,
|
||||
description,
|
||||
actions: [{
|
||||
name: refactorName,
|
||||
description
|
||||
}]
|
||||
}];
|
||||
}
|
||||
|
||||
function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined {
|
||||
Debug.assert(actionName === refactorName);
|
||||
const { file, startPosition, program, cancellationToken, host } = context;
|
||||
const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, program.getTypeChecker());
|
||||
if (!functionDeclaration || !cancellationToken) return undefined;
|
||||
|
||||
const groupedReferences = getGroupedReferences(functionDeclaration, program, cancellationToken);
|
||||
if (groupedReferences.valid) {
|
||||
const edits = textChanges.ChangeTracker.with(context, t => doChange(file, program, host, t, functionDeclaration, groupedReferences));
|
||||
return { renameFilename: undefined, renameLocation: undefined, edits };
|
||||
}
|
||||
|
||||
return { edits: [] }; // TODO: GH#30113
|
||||
}
|
||||
|
||||
function doChange(
|
||||
sourceFile: SourceFile,
|
||||
program: Program,
|
||||
host: LanguageServiceHost,
|
||||
changes: textChanges.ChangeTracker,
|
||||
functionDeclaration: ValidFunctionDeclaration,
|
||||
groupedReferences: GroupedReferences): void {
|
||||
const newParamDeclaration = map(createNewParameters(functionDeclaration, program, host), param => getSynthesizedDeepClone(param));
|
||||
changes.replaceNodeRangeWithNodes(
|
||||
sourceFile,
|
||||
first(functionDeclaration.parameters),
|
||||
last(functionDeclaration.parameters),
|
||||
newParamDeclaration,
|
||||
{ joiner: ", ",
|
||||
// indentation is set to 0 because otherwise the object parameter will be indented if there is a `this` parameter
|
||||
indentation: 0,
|
||||
leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll,
|
||||
trailingTriviaOption: textChanges.TrailingTriviaOption.Include
|
||||
});
|
||||
|
||||
const functionCalls = sortAndDeduplicate(groupedReferences.functionCalls, /*comparer*/ (a, b) => compareValues(a.pos, b.pos));
|
||||
for (const call of functionCalls) {
|
||||
if (call.arguments && call.arguments.length) {
|
||||
const newArgument = getSynthesizedDeepClone(createNewArgument(functionDeclaration, call.arguments), /*includeTrivia*/ true);
|
||||
changes.replaceNodeRange(
|
||||
getSourceFileOfNode(call),
|
||||
first(call.arguments),
|
||||
last(call.arguments),
|
||||
newArgument,
|
||||
{ leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll, trailingTriviaOption: textChanges.TrailingTriviaOption.Include });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getGroupedReferences(functionDeclaration: ValidFunctionDeclaration, program: Program, cancellationToken: CancellationToken): GroupedReferences {
|
||||
const functionNames = getFunctionNames(functionDeclaration);
|
||||
const classNames = isConstructorDeclaration(functionDeclaration) ? getClassNames(functionDeclaration) : [];
|
||||
const names = deduplicate([...functionNames, ...classNames], equateValues);
|
||||
const checker = program.getTypeChecker();
|
||||
|
||||
const references = flatMap(names, /*mapfn*/ name => FindAllReferences.getReferenceEntriesForNode(-1, name, program, program.getSourceFiles(), cancellationToken));
|
||||
const groupedReferences = groupReferences(references);
|
||||
|
||||
if (!every(groupedReferences.declarations, decl => contains(names, decl))) {
|
||||
groupedReferences.valid = false;
|
||||
}
|
||||
|
||||
return groupedReferences;
|
||||
|
||||
function groupReferences(referenceEntries: ReadonlyArray<FindAllReferences.Entry>): GroupedReferences {
|
||||
const classReferences: ClassReferences = { accessExpressions: [], typeUsages: [] };
|
||||
const groupedReferences: GroupedReferences = { functionCalls: [], declarations: [], classReferences, valid: true };
|
||||
const functionSymbols = map(functionNames, checker.getSymbolAtLocation);
|
||||
const classSymbols = map(classNames, checker.getSymbolAtLocation);
|
||||
const isConstructor = isConstructorDeclaration(functionDeclaration);
|
||||
|
||||
for (const entry of referenceEntries) {
|
||||
if (entry.kind !== FindAllReferences.EntryKind.Node) {
|
||||
groupedReferences.valid = false;
|
||||
continue;
|
||||
}
|
||||
if (contains(functionSymbols, checker.getSymbolAtLocation(entry.node), symbolComparer)) {
|
||||
const decl = entryToDeclaration(entry);
|
||||
if (decl) {
|
||||
groupedReferences.declarations.push(decl);
|
||||
continue;
|
||||
}
|
||||
|
||||
const call = entryToFunctionCall(entry);
|
||||
if (call) {
|
||||
groupedReferences.functionCalls.push(call);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// if the refactored function is a constructor, we must also check if the references to its class are valid
|
||||
if (isConstructor && contains(classSymbols, checker.getSymbolAtLocation(entry.node), symbolComparer)) {
|
||||
const decl = entryToDeclaration(entry);
|
||||
if (decl) {
|
||||
groupedReferences.declarations.push(decl);
|
||||
continue;
|
||||
}
|
||||
|
||||
const accessExpression = entryToAccessExpression(entry);
|
||||
if (accessExpression) {
|
||||
classReferences.accessExpressions.push(accessExpression);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only class declarations are allowed to be used as a type (in a heritage clause),
|
||||
// otherwise `findAllReferences` might not be able to track constructor calls.
|
||||
if (isClassDeclaration(functionDeclaration.parent)) {
|
||||
const type = entryToType(entry);
|
||||
if (type) {
|
||||
classReferences.typeUsages.push(type);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
groupedReferences.valid = false;
|
||||
}
|
||||
|
||||
return groupedReferences;
|
||||
}
|
||||
}
|
||||
|
||||
function symbolComparer(a: Symbol, b: Symbol): boolean {
|
||||
return getSymbolTarget(a) === getSymbolTarget(b);
|
||||
}
|
||||
|
||||
function entryToDeclaration(entry: FindAllReferences.NodeEntry): Node | undefined {
|
||||
if (isDeclaration(entry.node.parent)) {
|
||||
return entry.node;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function entryToFunctionCall(entry: FindAllReferences.NodeEntry): CallExpression | NewExpression | undefined {
|
||||
if (entry.node.parent) {
|
||||
const functionReference = entry.node;
|
||||
const parent = functionReference.parent;
|
||||
switch (parent.kind) {
|
||||
// Function call (foo(...) or super(...))
|
||||
case SyntaxKind.CallExpression:
|
||||
const callExpression = tryCast(parent, isCallExpression);
|
||||
if (callExpression && callExpression.expression === functionReference) {
|
||||
return callExpression;
|
||||
}
|
||||
break;
|
||||
// Constructor call (new Foo(...))
|
||||
case SyntaxKind.NewExpression:
|
||||
const newExpression = tryCast(parent, isNewExpression);
|
||||
if (newExpression && newExpression.expression === functionReference) {
|
||||
return newExpression;
|
||||
}
|
||||
break;
|
||||
// Method call (x.foo(...))
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression);
|
||||
if (propertyAccessExpression && propertyAccessExpression.parent && propertyAccessExpression.name === functionReference) {
|
||||
const callExpression = tryCast(propertyAccessExpression.parent, isCallExpression);
|
||||
if (callExpression && callExpression.expression === propertyAccessExpression) {
|
||||
return callExpression;
|
||||
}
|
||||
}
|
||||
break;
|
||||
// Method call (x["foo"](...))
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
const elementAccessExpression = tryCast(parent, isElementAccessExpression);
|
||||
if (elementAccessExpression && elementAccessExpression.parent && elementAccessExpression.argumentExpression === functionReference) {
|
||||
const callExpression = tryCast(elementAccessExpression.parent, isCallExpression);
|
||||
if (callExpression && callExpression.expression === elementAccessExpression) {
|
||||
return callExpression;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function entryToAccessExpression(entry: FindAllReferences.NodeEntry): ElementAccessExpression | PropertyAccessExpression | undefined {
|
||||
if (entry.node.parent) {
|
||||
const reference = entry.node;
|
||||
const parent = reference.parent;
|
||||
switch (parent.kind) {
|
||||
// `C.foo`
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression);
|
||||
if (propertyAccessExpression && propertyAccessExpression.expression === reference) {
|
||||
return propertyAccessExpression;
|
||||
}
|
||||
break;
|
||||
// `C["foo"]`
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
const elementAccessExpression = tryCast(parent, isElementAccessExpression);
|
||||
if (elementAccessExpression && elementAccessExpression.expression === reference) {
|
||||
return elementAccessExpression;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function entryToType(entry: FindAllReferences.NodeEntry): Node | undefined {
|
||||
const reference = entry.node;
|
||||
if (getMeaningFromLocation(reference) === SemanticMeaning.Type || isExpressionWithTypeArgumentsInClassExtendsClause(reference.parent)) {
|
||||
return reference;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getFunctionDeclarationAtPosition(file: SourceFile, startPosition: number, checker: TypeChecker): ValidFunctionDeclaration | undefined {
|
||||
const node = getTouchingToken(file, startPosition);
|
||||
const functionDeclaration = getContainingFunction(node);
|
||||
if (functionDeclaration
|
||||
&& isValidFunctionDeclaration(functionDeclaration, checker)
|
||||
&& rangeContainsRange(functionDeclaration, node)
|
||||
&& !(functionDeclaration.body && rangeContainsRange(functionDeclaration.body, node))) return functionDeclaration;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isValidFunctionDeclaration(functionDeclaration: SignatureDeclaration, checker: TypeChecker): functionDeclaration is ValidFunctionDeclaration {
|
||||
if (!isValidParameterNodeArray(functionDeclaration.parameters)) return false;
|
||||
switch (functionDeclaration.kind) {
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
return !!functionDeclaration.name && !!functionDeclaration.body && !checker.isImplementationOfOverload(functionDeclaration);
|
||||
case SyntaxKind.Constructor:
|
||||
if (isClassDeclaration(functionDeclaration.parent)) {
|
||||
return !!functionDeclaration.body && !!functionDeclaration.parent.name && !checker.isImplementationOfOverload(functionDeclaration);
|
||||
}
|
||||
else {
|
||||
return isValidVariableDeclaration(functionDeclaration.parent.parent) && !!functionDeclaration.body && !checker.isImplementationOfOverload(functionDeclaration);
|
||||
}
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
return isValidVariableDeclaration(functionDeclaration.parent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isValidParameterNodeArray(parameters: NodeArray<ParameterDeclaration>): parameters is ValidParameterNodeArray {
|
||||
return getRefactorableParametersLength(parameters) >= minimumParameterLength && every(parameters, isValidParameterDeclaration);
|
||||
}
|
||||
|
||||
function isValidParameterDeclaration(paramDeclaration: ParameterDeclaration): paramDeclaration is ValidParameterDeclaration {
|
||||
return !paramDeclaration.modifiers && !paramDeclaration.decorators && isIdentifier(paramDeclaration.name);
|
||||
}
|
||||
|
||||
function isValidVariableDeclaration(node: Node): node is ValidVariableDeclaration {
|
||||
return isVariableDeclaration(node) && isVarConst(node) && isIdentifier(node.name) && !node.type; // TODO: GH#30113
|
||||
}
|
||||
|
||||
function hasThisParameter(parameters: NodeArray<ParameterDeclaration>): boolean {
|
||||
return parameters.length > 0 && isThis(parameters[0].name);
|
||||
}
|
||||
|
||||
function getRefactorableParametersLength(parameters: NodeArray<ParameterDeclaration>): number {
|
||||
if (hasThisParameter(parameters)) {
|
||||
return parameters.length - 1;
|
||||
}
|
||||
return parameters.length;
|
||||
}
|
||||
|
||||
function getRefactorableParameters(parameters: NodeArray<ValidParameterDeclaration>): NodeArray<ValidParameterDeclaration> {
|
||||
if (hasThisParameter(parameters)) {
|
||||
parameters = createNodeArray(parameters.slice(1), parameters.hasTrailingComma);
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
function createNewArgument(functionDeclaration: ValidFunctionDeclaration, functionArguments: NodeArray<Expression>): ObjectLiteralExpression {
|
||||
const parameters = getRefactorableParameters(functionDeclaration.parameters);
|
||||
const hasRestParameter = isRestParameter(last(parameters));
|
||||
const nonRestArguments = hasRestParameter ? functionArguments.slice(0, parameters.length - 1) : functionArguments;
|
||||
const properties = map(nonRestArguments, (arg, i) => {
|
||||
const property = createPropertyAssignment(getParameterName(parameters[i]), arg);
|
||||
suppressLeadingAndTrailingTrivia(property.initializer);
|
||||
copyComments(arg, property);
|
||||
return property;
|
||||
});
|
||||
|
||||
if (hasRestParameter && functionArguments.length >= parameters.length) {
|
||||
const restArguments = functionArguments.slice(parameters.length - 1);
|
||||
const restProperty = createPropertyAssignment(getParameterName(last(parameters)), createArrayLiteral(restArguments));
|
||||
properties.push(restProperty);
|
||||
}
|
||||
|
||||
const objectLiteral = createObjectLiteral(properties, /*multiLine*/ false);
|
||||
return objectLiteral;
|
||||
}
|
||||
|
||||
function createNewParameters(functionDeclaration: ValidFunctionDeclaration, program: Program, host: LanguageServiceHost): NodeArray<ParameterDeclaration> {
|
||||
const refactorableParameters = getRefactorableParameters(functionDeclaration.parameters);
|
||||
const bindingElements = map(refactorableParameters, createBindingElementFromParameterDeclaration);
|
||||
const objectParameterName = createObjectBindingPattern(bindingElements);
|
||||
const objectParameterType = createParameterTypeNode(refactorableParameters);
|
||||
const checker = program.getTypeChecker();
|
||||
|
||||
let objectInitializer: Expression | undefined;
|
||||
// If every parameter in the original function was optional, add an empty object initializer to the new object parameter
|
||||
if (every(refactorableParameters, checker.isOptionalParameter)) {
|
||||
objectInitializer = createObjectLiteral();
|
||||
}
|
||||
|
||||
const objectParameter = createParameter(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
/*dotDotDotToken*/ undefined,
|
||||
objectParameterName,
|
||||
/*questionToken*/ undefined,
|
||||
objectParameterType,
|
||||
objectInitializer);
|
||||
|
||||
if (hasThisParameter(functionDeclaration.parameters)) {
|
||||
const thisParameter = functionDeclaration.parameters[0];
|
||||
const newThisParameter = createParameter(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
/*dotDotDotToken*/ undefined,
|
||||
thisParameter.name,
|
||||
/*questionToken*/ undefined,
|
||||
thisParameter.type);
|
||||
|
||||
suppressLeadingAndTrailingTrivia(newThisParameter.name);
|
||||
copyComments(thisParameter.name, newThisParameter.name);
|
||||
if (thisParameter.type) {
|
||||
suppressLeadingAndTrailingTrivia(newThisParameter.type!);
|
||||
copyComments(thisParameter.type, newThisParameter.type!);
|
||||
}
|
||||
|
||||
return createNodeArray([newThisParameter, objectParameter]);
|
||||
}
|
||||
return createNodeArray([objectParameter]);
|
||||
|
||||
function createParameterTypeNode(parameters: NodeArray<ValidParameterDeclaration>): TypeLiteralNode {
|
||||
const members = map(parameters, createPropertySignatureFromParameterDeclaration);
|
||||
const typeNode = addEmitFlags(createTypeLiteralNode(members), EmitFlags.SingleLine);
|
||||
return typeNode;
|
||||
}
|
||||
|
||||
function createPropertySignatureFromParameterDeclaration(parameterDeclaration: ValidParameterDeclaration): PropertySignature {
|
||||
let parameterType = parameterDeclaration.type;
|
||||
if (!parameterType && (parameterDeclaration.initializer || isRestParameter(parameterDeclaration))) {
|
||||
parameterType = getTypeNode(parameterDeclaration);
|
||||
}
|
||||
|
||||
const propertySignature = createPropertySignature(
|
||||
/*modifiers*/ undefined,
|
||||
getParameterName(parameterDeclaration),
|
||||
parameterDeclaration.initializer || isRestParameter(parameterDeclaration) ? createToken(SyntaxKind.QuestionToken) : parameterDeclaration.questionToken,
|
||||
parameterType,
|
||||
/*initializer*/ undefined);
|
||||
|
||||
suppressLeadingAndTrailingTrivia(propertySignature);
|
||||
copyComments(parameterDeclaration.name, propertySignature.name);
|
||||
if (parameterDeclaration.type && propertySignature.type) {
|
||||
copyComments(parameterDeclaration.type, propertySignature.type);
|
||||
}
|
||||
|
||||
return propertySignature;
|
||||
}
|
||||
|
||||
function getTypeNode(node: Node): TypeNode | undefined {
|
||||
const checker = program.getTypeChecker();
|
||||
const type = checker.getTypeAtLocation(node);
|
||||
return getTypeNodeIfAccessible(type, node, program, host);
|
||||
}
|
||||
}
|
||||
|
||||
function createBindingElementFromParameterDeclaration(parameterDeclaration: ValidParameterDeclaration): BindingElement {
|
||||
const element = createBindingElement(
|
||||
/*dotDotDotToken*/ undefined,
|
||||
/*propertyName*/ undefined,
|
||||
getParameterName(parameterDeclaration),
|
||||
isRestParameter(parameterDeclaration) ? createArrayLiteral() : parameterDeclaration.initializer);
|
||||
|
||||
suppressLeadingAndTrailingTrivia(element);
|
||||
if (parameterDeclaration.initializer && element.initializer) {
|
||||
copyComments(parameterDeclaration.initializer, element.initializer);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
function copyComments(sourceNode: Node, targetNode: Node) {
|
||||
const sourceFile = sourceNode.getSourceFile();
|
||||
const text = sourceFile.text;
|
||||
if (hasLeadingLineBreak(sourceNode, text)) {
|
||||
copyLeadingComments(sourceNode, targetNode, sourceFile);
|
||||
}
|
||||
else {
|
||||
copyTrailingAsLeadingComments(sourceNode, targetNode, sourceFile);
|
||||
}
|
||||
copyTrailingComments(sourceNode, targetNode, sourceFile);
|
||||
}
|
||||
|
||||
function hasLeadingLineBreak(node: Node, text: string) {
|
||||
const start = node.getFullStart();
|
||||
const end = node.getStart();
|
||||
for (let i = start; i < end; i++) {
|
||||
if (text.charCodeAt(i) === CharacterCodes.lineFeed) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getParameterName(paramDeclaration: ValidParameterDeclaration) {
|
||||
return getTextOfIdentifierOrLiteral(paramDeclaration.name);
|
||||
}
|
||||
|
||||
function getClassNames(constructorDeclaration: ValidConstructor): Identifier[] {
|
||||
switch (constructorDeclaration.parent.kind) {
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
const classDeclaration = constructorDeclaration.parent;
|
||||
return [classDeclaration.name];
|
||||
case SyntaxKind.ClassExpression:
|
||||
const classExpression = constructorDeclaration.parent;
|
||||
const variableDeclaration = constructorDeclaration.parent.parent;
|
||||
const className = classExpression.name;
|
||||
if (className) return [className, variableDeclaration.name];
|
||||
return [variableDeclaration.name];
|
||||
}
|
||||
}
|
||||
|
||||
function getFunctionNames(functionDeclaration: ValidFunctionDeclaration): Node[] {
|
||||
switch (functionDeclaration.kind) {
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
return [functionDeclaration.name];
|
||||
case SyntaxKind.Constructor:
|
||||
const ctrKeyword = findChildOfKind(functionDeclaration, SyntaxKind.ConstructorKeyword, functionDeclaration.getSourceFile())!;
|
||||
if (functionDeclaration.parent.kind === SyntaxKind.ClassExpression) {
|
||||
const variableDeclaration = functionDeclaration.parent.parent;
|
||||
return [variableDeclaration.name, ctrKeyword];
|
||||
}
|
||||
return [ctrKeyword];
|
||||
case SyntaxKind.ArrowFunction:
|
||||
return [functionDeclaration.parent.name];
|
||||
case SyntaxKind.FunctionExpression:
|
||||
if (functionDeclaration.name) return [functionDeclaration.name, functionDeclaration.parent.name];
|
||||
return [functionDeclaration.parent.name];
|
||||
default:
|
||||
return Debug.assertNever(functionDeclaration);
|
||||
}
|
||||
}
|
||||
|
||||
type ValidParameterNodeArray = NodeArray<ValidParameterDeclaration>;
|
||||
|
||||
interface ValidVariableDeclaration extends VariableDeclaration {
|
||||
name: Identifier;
|
||||
type: undefined;
|
||||
}
|
||||
|
||||
interface ValidConstructor extends ConstructorDeclaration {
|
||||
parent: (ClassDeclaration & { name: Identifier }) | (ClassExpression & { parent: ValidVariableDeclaration });
|
||||
parameters: NodeArray<ValidParameterDeclaration>;
|
||||
body: FunctionBody;
|
||||
}
|
||||
|
||||
interface ValidFunction extends FunctionDeclaration {
|
||||
name: Identifier;
|
||||
parameters: NodeArray<ValidParameterDeclaration>;
|
||||
body: FunctionBody;
|
||||
}
|
||||
|
||||
interface ValidMethod extends MethodDeclaration {
|
||||
parameters: NodeArray<ValidParameterDeclaration>;
|
||||
body: FunctionBody;
|
||||
}
|
||||
|
||||
interface ValidFunctionExpression extends FunctionExpression {
|
||||
parent: ValidVariableDeclaration;
|
||||
parameters: NodeArray<ValidParameterDeclaration>;
|
||||
}
|
||||
|
||||
interface ValidArrowFunction extends ArrowFunction {
|
||||
parent: ValidVariableDeclaration;
|
||||
parameters: NodeArray<ValidParameterDeclaration>;
|
||||
}
|
||||
|
||||
type ValidFunctionDeclaration = ValidConstructor | ValidFunction | ValidMethod | ValidArrowFunction | ValidFunctionExpression;
|
||||
|
||||
interface ValidParameterDeclaration extends ParameterDeclaration {
|
||||
name: Identifier;
|
||||
modifiers: undefined;
|
||||
decorators: undefined;
|
||||
}
|
||||
|
||||
interface GroupedReferences {
|
||||
functionCalls: (CallExpression | NewExpression)[];
|
||||
declarations: Node[];
|
||||
classReferences?: ClassReferences;
|
||||
valid: boolean;
|
||||
}
|
||||
interface ClassReferences {
|
||||
accessExpressions: Node[];
|
||||
typeUsages: Node[];
|
||||
}
|
||||
}
|
||||
@@ -1798,7 +1798,7 @@ namespace ts {
|
||||
const span = createTextSpanFromBounds(start, end);
|
||||
const formatContext = formatting.getFormatContext(formatOptions);
|
||||
|
||||
return flatMap(deduplicate(errorCodes, equateValues, compareValues), errorCode => {
|
||||
return flatMap(deduplicate<number>(errorCodes, equateValues, compareValues), errorCode => {
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
return codefix.getFixes({ errorCode, sourceFile, span, program, host, cancellationToken, formatContext, preferences });
|
||||
});
|
||||
|
||||
+42
-29
@@ -28,17 +28,27 @@ namespace ts.textChanges {
|
||||
}
|
||||
|
||||
export interface ConfigurableStart {
|
||||
/** True to use getStart() (NB, not getFullStart()) without adjustment. */
|
||||
useNonAdjustedStartPosition?: boolean;
|
||||
leadingTriviaOption?: LeadingTriviaOption;
|
||||
}
|
||||
export interface ConfigurableEnd {
|
||||
/** True to use getEnd() without adjustment. */
|
||||
useNonAdjustedEndPosition?: boolean;
|
||||
trailingTriviaOption?: TrailingTriviaOption;
|
||||
}
|
||||
|
||||
export enum Position {
|
||||
FullStart,
|
||||
Start
|
||||
export enum LeadingTriviaOption {
|
||||
/** Exclude all leading trivia (use getStart()) */
|
||||
Exclude,
|
||||
/** Include leading trivia and,
|
||||
* if there are no line breaks between the node and the previous token,
|
||||
* include all trivia between the node and the previous token
|
||||
*/
|
||||
IncludeAll,
|
||||
}
|
||||
|
||||
export enum TrailingTriviaOption {
|
||||
/** Exclude all trailing trivia (use getEnd()) */
|
||||
Exclude,
|
||||
/** Include trailing trivia */
|
||||
Include,
|
||||
}
|
||||
|
||||
function skipWhitespacesAndLineBreaks(text: string, start: number) {
|
||||
@@ -68,13 +78,14 @@ namespace ts.textChanges {
|
||||
* Usually leading trivia of the variable declaration 'y' should not include trailing trivia (whitespace, comment 'this is x' and newline) from the preceding
|
||||
* variable declaration and trailing trivia for 'y' should include (whitespace, comment 'this is y', newline).
|
||||
* By default when removing nodes we adjust start and end positions to respect specification of the trivia above.
|
||||
* If pos\end should be interpreted literally 'useNonAdjustedStartPosition' or 'useNonAdjustedEndPosition' should be set to true
|
||||
* If pos\end should be interpreted literally (that is, withouth including leading and trailing trivia), `leadingTriviaOption` should be set to `LeadingTriviaOption.Exclude`
|
||||
* and `trailingTriviaOption` to `TrailingTriviaOption.Exclude`.
|
||||
*/
|
||||
export interface ConfigurableStartEnd extends ConfigurableStart, ConfigurableEnd {}
|
||||
|
||||
export const useNonAdjustedPositions: ConfigurableStartEnd = {
|
||||
useNonAdjustedStartPosition: true,
|
||||
useNonAdjustedEndPosition: true,
|
||||
const useNonAdjustedPositions: ConfigurableStartEnd = {
|
||||
leadingTriviaOption: LeadingTriviaOption.Exclude,
|
||||
trailingTriviaOption: TrailingTriviaOption.Exclude,
|
||||
};
|
||||
|
||||
export interface InsertNodeOptions {
|
||||
@@ -143,11 +154,12 @@ namespace ts.textChanges {
|
||||
}
|
||||
|
||||
function getAdjustedRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd): TextRange {
|
||||
return { pos: getAdjustedStartPosition(sourceFile, startNode, options, Position.Start), end: getAdjustedEndPosition(sourceFile, endNode, options) };
|
||||
return { pos: getAdjustedStartPosition(sourceFile, startNode, options), end: getAdjustedEndPosition(sourceFile, endNode, options) };
|
||||
}
|
||||
|
||||
function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStart, position: Position) {
|
||||
if (options.useNonAdjustedStartPosition) {
|
||||
function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStart) {
|
||||
const { leadingTriviaOption } = options;
|
||||
if (leadingTriviaOption === LeadingTriviaOption.Exclude) {
|
||||
return node.getStart(sourceFile);
|
||||
}
|
||||
const fullStart = node.getFullStart();
|
||||
@@ -165,7 +177,7 @@ namespace ts.textChanges {
|
||||
// fullstart
|
||||
// when b is replaced - we usually want to keep the leading trvia
|
||||
// when b is deleted - we delete it
|
||||
return position === Position.Start ? start : fullStart;
|
||||
return leadingTriviaOption === LeadingTriviaOption.IncludeAll ? fullStart : start;
|
||||
}
|
||||
// get start position of the line following the line that contains fullstart position
|
||||
// (but only if the fullstart isn't the very beginning of the file)
|
||||
@@ -178,11 +190,12 @@ namespace ts.textChanges {
|
||||
|
||||
function getAdjustedEndPosition(sourceFile: SourceFile, node: Node, options: ConfigurableEnd) {
|
||||
const { end } = node;
|
||||
if (options.useNonAdjustedEndPosition || isExpression(node)) {
|
||||
const { trailingTriviaOption } = options;
|
||||
if (trailingTriviaOption === TrailingTriviaOption.Exclude || (isExpression(node) && trailingTriviaOption !== TrailingTriviaOption.Include)) {
|
||||
return end;
|
||||
}
|
||||
const newEnd = skipTrivia(sourceFile.text, end, /*stopAfterLineBreak*/ true);
|
||||
return newEnd !== end && isLineBreak(sourceFile.text.charCodeAt(newEnd - 1))
|
||||
return newEnd !== end && (trailingTriviaOption === TrailingTriviaOption.Include || isLineBreak(sourceFile.text.charCodeAt(newEnd - 1)))
|
||||
? newEnd
|
||||
: end;
|
||||
}
|
||||
@@ -240,15 +253,15 @@ namespace ts.textChanges {
|
||||
this.deleteRange(sourceFile, { pos: modifier.getStart(sourceFile), end: skipTrivia(sourceFile.text, modifier.end, /*stopAfterLineBreak*/ true) });
|
||||
}
|
||||
|
||||
public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = {}): void {
|
||||
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.FullStart);
|
||||
public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void {
|
||||
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options);
|
||||
const endPosition = getAdjustedEndPosition(sourceFile, endNode, options);
|
||||
this.deleteRange(sourceFile, { pos: startPosition, end: endPosition });
|
||||
}
|
||||
|
||||
public deleteNodeRangeExcludingEnd(sourceFile: SourceFile, startNode: Node, afterEndNode: Node | undefined, options: ConfigurableStartEnd = {}): void {
|
||||
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.FullStart);
|
||||
const endPosition = afterEndNode === undefined ? sourceFile.text.length : getAdjustedStartPosition(sourceFile, afterEndNode, options, Position.FullStart);
|
||||
public deleteNodeRangeExcludingEnd(sourceFile: SourceFile, startNode: Node, afterEndNode: Node | undefined, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void {
|
||||
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options);
|
||||
const endPosition = afterEndNode === undefined ? sourceFile.text.length : getAdjustedStartPosition(sourceFile, afterEndNode, options);
|
||||
this.deleteRange(sourceFile, { pos: startPosition, end: endPosition });
|
||||
}
|
||||
|
||||
@@ -307,7 +320,7 @@ namespace ts.textChanges {
|
||||
}
|
||||
|
||||
public insertNodeBefore(sourceFile: SourceFile, before: Node, newNode: Node, blankLineBetween = false): void {
|
||||
this.insertNodeAt(sourceFile, getAdjustedStartPosition(sourceFile, before, {}, Position.Start), newNode, this.getOptionsForInsertNodeBefore(before, blankLineBetween));
|
||||
this.insertNodeAt(sourceFile, getAdjustedStartPosition(sourceFile, before, {}), newNode, this.getOptionsForInsertNodeBefore(before, blankLineBetween));
|
||||
}
|
||||
|
||||
public insertModifierBefore(sourceFile: SourceFile, modifier: SyntaxKind, before: Node): void {
|
||||
@@ -427,7 +440,7 @@ namespace ts.textChanges {
|
||||
}
|
||||
|
||||
public insertNodeAtEndOfScope(sourceFile: SourceFile, scope: Node, newNode: Node): void {
|
||||
const pos = getAdjustedStartPosition(sourceFile, scope.getLastToken()!, {}, Position.Start);
|
||||
const pos = getAdjustedStartPosition(sourceFile, scope.getLastToken()!, {});
|
||||
this.insertNodeAt(sourceFile, pos, newNode, {
|
||||
prefix: isLineBreak(sourceFile.text.charCodeAt(scope.getLastToken()!.pos)) ? this.newLineCharacter : this.newLineCharacter + this.newLineCharacter,
|
||||
suffix: this.newLineCharacter
|
||||
@@ -736,7 +749,7 @@ namespace ts.textChanges {
|
||||
|
||||
// find first non-whitespace position in the leading trivia of the node
|
||||
function startPositionToDeleteNodeInList(sourceFile: SourceFile, node: Node): number {
|
||||
return skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
|
||||
return skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, { leadingTriviaOption: LeadingTriviaOption.IncludeAll }), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
|
||||
}
|
||||
|
||||
function getClassOrObjectBraceEnds(cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, sourceFile: SourceFile): [number, number] {
|
||||
@@ -1090,7 +1103,7 @@ namespace ts.textChanges {
|
||||
case SyntaxKind.ImportDeclaration:
|
||||
deleteNode(changes, sourceFile, node,
|
||||
// For first import, leave header comment in place
|
||||
node === sourceFile.imports[0].parent ? { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: false } : undefined);
|
||||
node === sourceFile.imports[0].parent ? { leadingTriviaOption: LeadingTriviaOption.Exclude } : undefined);
|
||||
break;
|
||||
|
||||
case SyntaxKind.BindingElement:
|
||||
@@ -1134,7 +1147,7 @@ namespace ts.textChanges {
|
||||
deleteNodeInList(changes, deletedNodesInLists, sourceFile, node);
|
||||
}
|
||||
else {
|
||||
deleteNode(changes, sourceFile, node, node.kind === SyntaxKind.SemicolonToken ? { useNonAdjustedEndPosition: true } : undefined);
|
||||
deleteNode(changes, sourceFile, node, node.kind === SyntaxKind.SemicolonToken ? { trailingTriviaOption: TrailingTriviaOption.Exclude } : undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1213,8 +1226,8 @@ namespace ts.textChanges {
|
||||
|
||||
/** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */
|
||||
// Exported for tests only! (TODO: improve tests to not need this)
|
||||
export function deleteNode(changes: ChangeTracker, sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = {}): void {
|
||||
const startPosition = getAdjustedStartPosition(sourceFile, node, options, Position.FullStart);
|
||||
export function deleteNode(changes: ChangeTracker, sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void {
|
||||
const startPosition = getAdjustedStartPosition(sourceFile, node, options);
|
||||
const endPosition = getAdjustedEndPosition(sourceFile, node, options);
|
||||
changes.deleteRange(sourceFile, { pos: startPosition, end: endPosition });
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
"refactors/generateGetAccessorAndSetAccessor.ts",
|
||||
"refactors/moveToNewFile.ts",
|
||||
"refactors/addOrRemoveBracesToArrowFunction.ts",
|
||||
"refactors/convertToNamedParameters.ts",
|
||||
"services.ts",
|
||||
"breakpoints.ts",
|
||||
"transform.ts",
|
||||
|
||||
@@ -1664,6 +1664,18 @@ namespace ts {
|
||||
return ensureScriptKind(fileName, host && host.getScriptKind && host.getScriptKind(fileName));
|
||||
}
|
||||
|
||||
export function getSymbolTarget(symbol: Symbol): Symbol {
|
||||
let next: Symbol = symbol;
|
||||
while (isTransientSymbol(next) && next.target) {
|
||||
next = next.target;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
function isTransientSymbol(symbol: Symbol): symbol is TransientSymbol {
|
||||
return (symbol.flags & SymbolFlags.Transient) !== 0;
|
||||
}
|
||||
|
||||
export function getUniqueSymbolId(symbol: Symbol, checker: TypeChecker) {
|
||||
return getSymbolId(skipAlias(symbol, checker));
|
||||
}
|
||||
@@ -1821,8 +1833,28 @@ namespace ts {
|
||||
return lastPos;
|
||||
}
|
||||
|
||||
export function copyComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) {
|
||||
forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, (pos, end, kind, htnl) => {
|
||||
export function copyLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) {
|
||||
forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment));
|
||||
}
|
||||
|
||||
|
||||
export function copyTrailingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) {
|
||||
forEachTrailingCommentRange(sourceFile.text, sourceNode.end, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticTrailingComment));
|
||||
}
|
||||
|
||||
/**
|
||||
* This function copies the trailing comments for the token that comes before `sourceNode`, as leading comments of `targetNode`.
|
||||
* This is useful because sometimes a comment that refers to `sourceNode` will be a leading comment for `sourceNode`, according to the
|
||||
* notion of trivia ownership, and instead will be a trailing comment for the token before `sourceNode`, e.g.:
|
||||
* `function foo(\* not leading comment for a *\ a: string) {}`
|
||||
* The comment refers to `a` but belongs to the `(` token, but we might want to copy it.
|
||||
*/
|
||||
export function copyTrailingAsLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) {
|
||||
forEachTrailingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment));
|
||||
}
|
||||
|
||||
function getAddCommentsFunction(targetNode: Node, sourceFile: SourceFile, commentKind: CommentKind | undefined, hasTrailingNewLine: boolean | undefined, cb: (node: Node, kind: CommentKind, text: string, hasTrailingNewLine?: boolean) => void) {
|
||||
return (pos: number, end: number, kind: CommentKind, htnl: boolean) => {
|
||||
if (kind === SyntaxKind.MultiLineCommentTrivia) {
|
||||
// Remove leading /*
|
||||
pos += 2;
|
||||
@@ -1833,8 +1865,8 @@ namespace ts {
|
||||
// Remove leading //
|
||||
pos += 2;
|
||||
}
|
||||
addSyntheticLeadingComment(targetNode, commentKind || kind, sourceFile.text.slice(pos, end), hasTrailingNewLine !== undefined ? hasTrailingNewLine : htnl);
|
||||
});
|
||||
cb(targetNode, commentKind || kind, sourceFile.text.slice(pos, end), hasTrailingNewLine !== undefined ? hasTrailingNewLine : htnl);
|
||||
};
|
||||
}
|
||||
|
||||
function indexInTextChange(change: string, name: string): number {
|
||||
@@ -1914,4 +1946,28 @@ namespace ts {
|
||||
export function getSwitchedType(caseClause: CaseClause, checker: TypeChecker): Type | undefined {
|
||||
return checker.getTypeAtLocation(caseClause.parent.parent.expression);
|
||||
}
|
||||
|
||||
export function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined {
|
||||
const checker = program.getTypeChecker();
|
||||
let typeIsAccessible = true;
|
||||
const notAccessible = () => { typeIsAccessible = false; };
|
||||
const res = checker.typeToTypeNode(type, enclosingScope, /*flags*/ undefined, {
|
||||
trackSymbol: (symbol, declaration, meaning) => {
|
||||
// TODO: GH#18217
|
||||
typeIsAccessible = typeIsAccessible && checker.isSymbolAccessible(symbol, declaration, meaning!, /*shouldComputeAliasToMarkVisible*/ false).accessibility === SymbolAccessibility.Accessible;
|
||||
},
|
||||
reportInaccessibleThisError: notAccessible,
|
||||
reportPrivateInBaseOfClassExpression: notAccessible,
|
||||
reportInaccessibleUniqueSymbolError: notAccessible,
|
||||
moduleResolverHost: {
|
||||
readFile: host.readFile,
|
||||
fileExists: host.fileExists,
|
||||
directoryExists: host.directoryExists,
|
||||
getSourceFiles: program.getSourceFiles,
|
||||
getCurrentDirectory: program.getCurrentDirectory,
|
||||
getCommonSourceDirectory: program.getCommonSourceDirectory,
|
||||
}
|
||||
});
|
||||
return typeIsAccessible ? res : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,25 +538,23 @@ namespace Harness.Parallel.Host {
|
||||
|
||||
let xunitReporter: import("mocha").reporters.XUnit | undefined;
|
||||
let failedTestReporter: import("../../../scripts/failed-tests") | undefined;
|
||||
if (Utils.getExecutionEnvironment() !== Utils.ExecutionEnvironment.Browser) {
|
||||
if (process.env.CI === "true") {
|
||||
xunitReporter = new Mocha.reporters.XUnit(replayRunner, {
|
||||
reporterOptions: {
|
||||
suiteName: "Tests",
|
||||
output: "./TEST-results.xml"
|
||||
}
|
||||
});
|
||||
patchStats(xunitReporter.stats);
|
||||
xunitReporter.write(`<?xml version="1.0" encoding="UTF-8"?>\n`);
|
||||
}
|
||||
else {
|
||||
failedTestReporter = new FailedTestReporter(replayRunner, {
|
||||
reporterOptions: {
|
||||
file: path.resolve(".failed-tests"),
|
||||
keepFailed
|
||||
}
|
||||
});
|
||||
}
|
||||
if (process.env.CI === "true") {
|
||||
xunitReporter = new Mocha.reporters.XUnit(replayRunner, {
|
||||
reporterOptions: {
|
||||
suiteName: "Tests",
|
||||
output: "./TEST-results.xml"
|
||||
}
|
||||
});
|
||||
patchStats(xunitReporter.stats);
|
||||
xunitReporter.write(`<?xml version="1.0" encoding="UTF-8"?>\n`);
|
||||
}
|
||||
else {
|
||||
failedTestReporter = new FailedTestReporter(replayRunner, {
|
||||
reporterOptions: {
|
||||
file: path.resolve(".failed-tests"),
|
||||
keepFailed
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const savedUseColors = Base.useColors;
|
||||
|
||||
@@ -182,10 +182,7 @@ function handleTestConfig() {
|
||||
runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance));
|
||||
runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions));
|
||||
|
||||
// TODO: project tests don"t work in the browser yet
|
||||
if (Utils.getExecutionEnvironment() !== Utils.ExecutionEnvironment.Browser) {
|
||||
runners.push(new project.ProjectRunner());
|
||||
}
|
||||
runners.push(new project.ProjectRunner());
|
||||
|
||||
// language services
|
||||
runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Native));
|
||||
@@ -195,7 +192,7 @@ function handleTestConfig() {
|
||||
// runners.push(new GeneratedFourslashRunner());
|
||||
|
||||
// CRON-only tests
|
||||
if (Utils.getExecutionEnvironment() !== Utils.ExecutionEnvironment.Browser && process.env.TRAVIS_EVENT_TYPE === "cron") {
|
||||
if (process.env.TRAVIS_EVENT_TYPE === "cron") {
|
||||
runners.push(new UserCodeRunner());
|
||||
}
|
||||
}
|
||||
@@ -229,13 +226,11 @@ function beginTests() {
|
||||
let isWorker: boolean;
|
||||
function startTestEnvironment() {
|
||||
isWorker = handleTestConfig();
|
||||
if (Utils.getExecutionEnvironment() !== Utils.ExecutionEnvironment.Browser) {
|
||||
if (isWorker) {
|
||||
return Harness.Parallel.Worker.start();
|
||||
}
|
||||
else if (taskConfigsFolder && workerCount && workerCount > 1) {
|
||||
return Harness.Parallel.Host.start();
|
||||
}
|
||||
if (isWorker) {
|
||||
return Harness.Parallel.Worker.start();
|
||||
}
|
||||
else if (taskConfigsFolder && workerCount && workerCount > 1) {
|
||||
return Harness.Parallel.Host.start();
|
||||
}
|
||||
beginTests();
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
"unittests/tscWatch/resolutionCache.ts",
|
||||
"unittests/tscWatch/watchEnvironment.ts",
|
||||
"unittests/tscWatch/watchApi.ts",
|
||||
"unittests/tsserver/applyChangesToOpenFiles.ts",
|
||||
"unittests/tsserver/cachingFileSystemInformation.ts",
|
||||
"unittests/tsserver/cancellationToken.ts",
|
||||
"unittests/tsserver/compileOnSave.ts",
|
||||
|
||||
@@ -140,13 +140,13 @@ var z = 3; // comment 4
|
||||
deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile));
|
||||
});
|
||||
runSingleFileTest("deleteNode2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
|
||||
deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedStartPosition: true });
|
||||
deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude });
|
||||
});
|
||||
runSingleFileTest("deleteNode3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
|
||||
deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedEndPosition: true });
|
||||
deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude });
|
||||
});
|
||||
runSingleFileTest("deleteNode4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
|
||||
deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
|
||||
deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude });
|
||||
});
|
||||
runSingleFileTest("deleteNode5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
|
||||
deleteNode(changeTracker, sourceFile, findVariableStatementContaining("x", sourceFile));
|
||||
@@ -167,15 +167,15 @@ var a = 4; // comment 7
|
||||
});
|
||||
runSingleFileTest("deleteNodeRange2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
|
||||
changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile),
|
||||
{ useNonAdjustedStartPosition: true });
|
||||
{ leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude });
|
||||
});
|
||||
runSingleFileTest("deleteNodeRange3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
|
||||
changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile),
|
||||
{ useNonAdjustedEndPosition: true });
|
||||
{ trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude });
|
||||
});
|
||||
runSingleFileTest("deleteNodeRange4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
|
||||
changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile),
|
||||
{ useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
|
||||
{ leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude });
|
||||
});
|
||||
}
|
||||
function createTestVariableDeclaration(name: string) {
|
||||
@@ -254,16 +254,16 @@ var a = 4; // comment 7`;
|
||||
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter });
|
||||
});
|
||||
runSingleFileTest("replaceNode2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
|
||||
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, suffix: newLineCharacter, prefix: newLineCharacter });
|
||||
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, suffix: newLineCharacter, prefix: newLineCharacter });
|
||||
});
|
||||
runSingleFileTest("replaceNode3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
|
||||
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, suffix: newLineCharacter });
|
||||
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude, suffix: newLineCharacter });
|
||||
});
|
||||
runSingleFileTest("replaceNode4", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
|
||||
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
|
||||
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude });
|
||||
});
|
||||
runSingleFileTest("replaceNode5", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
|
||||
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("x", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
|
||||
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("x", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude });
|
||||
});
|
||||
}
|
||||
{
|
||||
@@ -279,13 +279,13 @@ var a = 4; // comment 7`;
|
||||
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { suffix: newLineCharacter });
|
||||
});
|
||||
runSingleFileTest("replaceNodeRange2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
|
||||
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, suffix: newLineCharacter, prefix: newLineCharacter });
|
||||
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, suffix: newLineCharacter, prefix: newLineCharacter });
|
||||
});
|
||||
runSingleFileTest("replaceNodeRange3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
|
||||
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, suffix: newLineCharacter });
|
||||
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude, suffix: newLineCharacter });
|
||||
});
|
||||
runSingleFileTest("replaceNodeRange4", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
|
||||
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
|
||||
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude });
|
||||
});
|
||||
}
|
||||
{
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
namespace ts.projectSystem {
|
||||
describe("unittests:: tsserver:: applyChangesToOpenFiles", () => {
|
||||
const configFile: File = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: "{}"
|
||||
};
|
||||
const file3: File = {
|
||||
path: "/a/b/file3.ts",
|
||||
content: "let xyz = 1;"
|
||||
};
|
||||
const app: File = {
|
||||
path: "/a/b/app.ts",
|
||||
content: "let z = 1;"
|
||||
};
|
||||
|
||||
function fileContentWithComment(file: File) {
|
||||
return `// some copy right notice
|
||||
${file.content}`;
|
||||
}
|
||||
|
||||
function verifyText(service: server.ProjectService, file: string, expected: string) {
|
||||
const info = service.getScriptInfo(file)!;
|
||||
const snap = info.getSnapshot();
|
||||
// Verified applied in reverse order
|
||||
assert.equal(snap.getText(0, snap.getLength()), expected, `Text of changed file: ${file}`);
|
||||
}
|
||||
|
||||
function verifyProjectVersion(project: server.Project, expected: number) {
|
||||
assert.equal(Number(project.getProjectVersion()), expected);
|
||||
}
|
||||
|
||||
function verify(applyChangesToOpen: (session: TestSession) => void) {
|
||||
const host = createServerHost([app, file3, commonFile1, commonFile2, libFile, configFile]);
|
||||
const session = createSession(host);
|
||||
session.executeCommandSeq<protocol.OpenRequest>({
|
||||
command: protocol.CommandTypes.Open,
|
||||
arguments: { file: app.path }
|
||||
});
|
||||
const service = session.getProjectService();
|
||||
const project = service.configuredProjects.get(configFile.path)!;
|
||||
assert.isDefined(project);
|
||||
verifyProjectVersion(project, 1);
|
||||
session.executeCommandSeq<protocol.OpenRequest>({
|
||||
command: protocol.CommandTypes.Open,
|
||||
arguments: {
|
||||
file: file3.path,
|
||||
fileContent: fileContentWithComment(file3)
|
||||
}
|
||||
});
|
||||
verifyProjectVersion(project, 2);
|
||||
|
||||
// Verify Texts
|
||||
verifyText(service, commonFile1.path, commonFile1.content);
|
||||
verifyText(service, commonFile2.path, commonFile2.content);
|
||||
verifyText(service, app.path, app.content);
|
||||
verifyText(service, file3.path, fileContentWithComment(file3));
|
||||
|
||||
// Apply changes
|
||||
applyChangesToOpen(session);
|
||||
|
||||
// Verify again
|
||||
verifyProjectVersion(project, 3);
|
||||
// Open file contents
|
||||
verifyText(service, commonFile1.path, fileContentWithComment(commonFile1));
|
||||
verifyText(service, commonFile2.path, fileContentWithComment(commonFile2));
|
||||
verifyText(service, app.path, "let zzz = 10;let zz = 10;let z = 1;");
|
||||
verifyText(service, file3.path, file3.content);
|
||||
}
|
||||
|
||||
it("with applyChangedToOpenFiles request", () => {
|
||||
verify(session =>
|
||||
session.executeCommandSeq<protocol.ApplyChangedToOpenFilesRequest>({
|
||||
command: protocol.CommandTypes.ApplyChangedToOpenFiles,
|
||||
arguments: {
|
||||
openFiles: [
|
||||
{
|
||||
fileName: commonFile1.path,
|
||||
content: fileContentWithComment(commonFile1)
|
||||
},
|
||||
{
|
||||
fileName: commonFile2.path,
|
||||
content: fileContentWithComment(commonFile2)
|
||||
}
|
||||
],
|
||||
changedFiles: [
|
||||
{
|
||||
fileName: app.path,
|
||||
changes: [
|
||||
{
|
||||
span: { start: 0, length: 0 },
|
||||
newText: "let zzz = 10;"
|
||||
},
|
||||
{
|
||||
span: { start: 0, length: 0 },
|
||||
newText: "let zz = 10;"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
closedFiles: [
|
||||
file3.path
|
||||
]
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("with updateOpen request", () => {
|
||||
verify(session =>
|
||||
session.executeCommandSeq<protocol.UpdateOpenRequest>({
|
||||
command: protocol.CommandTypes.UpdateOpen,
|
||||
arguments: {
|
||||
openFiles: [
|
||||
{
|
||||
file: commonFile1.path,
|
||||
fileContent: fileContentWithComment(commonFile1)
|
||||
},
|
||||
{
|
||||
file: commonFile2.path,
|
||||
fileContent: fileContentWithComment(commonFile2)
|
||||
}
|
||||
],
|
||||
changedFiles: [
|
||||
{
|
||||
fileName: app.path,
|
||||
textChanges: [
|
||||
{
|
||||
start: { line: 1, offset: 1 },
|
||||
end: { line: 1, offset: 1 },
|
||||
newText: "let zzz = 10;",
|
||||
},
|
||||
{
|
||||
start: { line: 1, offset: 1 },
|
||||
end: { line: 1, offset: 1 },
|
||||
newText: "let zz = 10;",
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
closedFiles: [
|
||||
file3.path
|
||||
]
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -41,13 +41,13 @@ namespace ts.projectSystem {
|
||||
|
||||
function changeFileToNotImportModule(service: TestProjectService) {
|
||||
const info = service.getScriptInfo(file.path)!;
|
||||
service.applyChangesToFile(info, [{ span: { start: 0, length: importModuleContent.length }, newText: "" }]);
|
||||
service.applyChangesToFile(info, singleIterator({ span: { start: 0, length: importModuleContent.length }, newText: "" }));
|
||||
checkProject(service, /*moduleIsOrphan*/ true);
|
||||
}
|
||||
|
||||
function changeFileToImportModule(service: TestProjectService) {
|
||||
const info = service.getScriptInfo(file.path)!;
|
||||
service.applyChangesToFile(info, [{ span: { start: 0, length: 0 }, newText: importModuleContent }]);
|
||||
service.applyChangesToFile(info, singleIterator({ span: { start: 0, length: 0 }, newText: importModuleContent }));
|
||||
checkProject(service, /*moduleIsOrphan*/ false);
|
||||
}
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ namespace ts.projectSystem {
|
||||
checkNumberOfInferredProjects(projectService, 0);
|
||||
|
||||
externalFiles[0].content = "let x =1;";
|
||||
projectService.applyChangesInOpenFiles(externalFiles, [], []);
|
||||
projectService.applyChangesInOpenFiles(arrayIterator(externalFiles));
|
||||
});
|
||||
|
||||
it("external project that included config files", () => {
|
||||
@@ -790,9 +790,7 @@ namespace ts.projectSystem {
|
||||
rootFiles: [{ fileName: tsconfig.path }, { fileName: jsFilePath }],
|
||||
options: { allowJs: false }
|
||||
}]);
|
||||
service.applyChangesInOpenFiles([
|
||||
{ fileName: jsFilePath, scriptKind: ScriptKind.JS, content: "" }
|
||||
], /*changedFiles*/ undefined, /*closedFiles*/ undefined);
|
||||
service.applyChangesInOpenFiles(singleIterator({ fileName: jsFilePath, scriptKind: ScriptKind.JS, content: "" }));
|
||||
checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 });
|
||||
checkProjectActualFiles(configProject, [tsconfig.path]);
|
||||
const inferredProject = service.inferredProjects[0];
|
||||
|
||||
@@ -202,7 +202,7 @@ namespace ts.projectSystem {
|
||||
|
||||
const host = createServerHost([file1, config1]);
|
||||
const projectService = createProjectService(host, { useSingleInferredProject: true }, { syntaxOnly: true });
|
||||
projectService.applyChangesInOpenFiles([{ fileName: file1.path, content: file1.content }], [], []);
|
||||
projectService.applyChangesInOpenFiles(singleIterator({ fileName: file1.path, content: file1.content }));
|
||||
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
||||
const proj = projectService.inferredProjects[0];
|
||||
@@ -588,11 +588,11 @@ namespace ts.projectSystem {
|
||||
|
||||
const host = createServerHost([]);
|
||||
const projectService = createProjectService(host);
|
||||
projectService.applyChangesInOpenFiles([tsFile], [], []);
|
||||
projectService.applyChangesInOpenFiles(singleIterator(tsFile));
|
||||
const projs = projectService.synchronizeProjectList([]);
|
||||
projectService.findProject(projs[0].info!.projectName)!.getLanguageService().getNavigationBarItems(tsFile.fileName);
|
||||
projectService.synchronizeProjectList([projs[0].info!]);
|
||||
projectService.applyChangesInOpenFiles([jsFile], [], []);
|
||||
projectService.applyChangesInOpenFiles(singleIterator(jsFile));
|
||||
});
|
||||
|
||||
it("config file is deleted", () => {
|
||||
@@ -696,11 +696,12 @@ namespace ts.projectSystem {
|
||||
checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]);
|
||||
|
||||
// Open HTML file
|
||||
projectService.applyChangesInOpenFiles(
|
||||
/*openFiles*/[{ fileName: file2.path, hasMixedContent: true, scriptKind: ScriptKind.JS, content: `var hello = "hello";` }],
|
||||
/*changedFiles*/ undefined,
|
||||
/*closedFiles*/ undefined);
|
||||
|
||||
projectService.applyChangesInOpenFiles(singleIterator({
|
||||
fileName: file2.path,
|
||||
hasMixedContent: true,
|
||||
scriptKind: ScriptKind.JS,
|
||||
content: `var hello = "hello";`
|
||||
}));
|
||||
// Now HTML file is included in the project
|
||||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||
checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]);
|
||||
@@ -853,7 +854,7 @@ namespace ts.projectSystem {
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
||||
projectService.applyChangesInOpenFiles(
|
||||
/*openFiles*/ undefined,
|
||||
/*changedFiles*/[{ fileName: file1.path, changes: [{ span: createTextSpan(0, file1.path.length), newText: "let y = 1" }] }],
|
||||
/*changedFiles*/singleIterator({ fileName: file1.path, changes: singleIterator({ span: createTextSpan(0, file1.path.length), newText: "let y = 1" }) }),
|
||||
/*closedFiles*/ undefined);
|
||||
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
||||
|
||||
@@ -132,4 +132,79 @@ namespace ts.projectSystem {
|
||||
verifyRootedDirectoryWatch("c:/users/username/");
|
||||
});
|
||||
});
|
||||
|
||||
it(`unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem recursive watch directory implementation does not watch files/directories in node_modules starting with "."`, () => {
|
||||
const projectFolder = "/a/username/project";
|
||||
const projectSrcFolder = `${projectFolder}/src`;
|
||||
const configFile: File = {
|
||||
path: `${projectFolder}/tsconfig.json`,
|
||||
content: "{}"
|
||||
};
|
||||
const index: File = {
|
||||
path: `${projectSrcFolder}/index.ts`,
|
||||
content: `import {} from "file"`
|
||||
};
|
||||
const file1: File = {
|
||||
path: `${projectSrcFolder}/file1.ts`,
|
||||
content: ""
|
||||
};
|
||||
const nodeModulesExistingUnusedFile: File = {
|
||||
path: `${projectFolder}/node_modules/someFile.d.ts`,
|
||||
content: ""
|
||||
};
|
||||
|
||||
const fileNames = [index, file1, configFile, libFile].map(file => file.path);
|
||||
// All closed files(files other than index), project folder, project/src folder and project/node_modules/@types folder
|
||||
const expectedWatchedFiles = arrayToMap(fileNames.slice(1), identity, () => 1);
|
||||
const expectedWatchedDirectories = arrayToMap([projectFolder, projectSrcFolder, `${projectFolder}/${nodeModules}`, `${projectFolder}/${nodeModulesAtTypes}`], identity, () => 1);
|
||||
|
||||
const environmentVariables = createMap<string>();
|
||||
environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory);
|
||||
const host = createServerHost([index, file1, configFile, libFile, nodeModulesExistingUnusedFile], { environmentVariables });
|
||||
const projectService = createProjectService(host);
|
||||
projectService.openClientFile(index.path);
|
||||
|
||||
const project = Debug.assertDefined(projectService.configuredProjects.get(configFile.path));
|
||||
verifyProject();
|
||||
|
||||
const nodeModulesIgnoredFileFromIgnoreDirectory: File = {
|
||||
path: `${projectFolder}/node_modules/.cache/someFile.d.ts`,
|
||||
content: ""
|
||||
};
|
||||
host.ensureFileOrFolder(nodeModulesIgnoredFileFromIgnoreDirectory);
|
||||
host.checkTimeoutQueueLength(0);
|
||||
verifyProject();
|
||||
|
||||
const nodeModulesIgnoredFile: File = {
|
||||
path: `${projectFolder}/node_modules/.cacheFile.ts`,
|
||||
content: ""
|
||||
};
|
||||
host.ensureFileOrFolder(nodeModulesIgnoredFile);
|
||||
host.checkTimeoutQueueLength(0);
|
||||
verifyProject();
|
||||
|
||||
const gitIgnoredFileFromIgnoreDirectory: File = {
|
||||
path: `${projectFolder}/.git/someFile.d.ts`,
|
||||
content: ""
|
||||
};
|
||||
host.ensureFileOrFolder(gitIgnoredFileFromIgnoreDirectory);
|
||||
host.checkTimeoutQueueLength(0);
|
||||
verifyProject();
|
||||
|
||||
const gitIgnoredFile: File = {
|
||||
path: `${projectFolder}/.gitCache.d.ts`,
|
||||
content: ""
|
||||
};
|
||||
host.ensureFileOrFolder(gitIgnoredFile);
|
||||
host.checkTimeoutQueueLength(0);
|
||||
verifyProject();
|
||||
|
||||
function verifyProject() {
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
|
||||
checkWatchedFilesDetailed(host, expectedWatchedFiles);
|
||||
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ false);
|
||||
checkProjectActualFiles(project, fileNames);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user