diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts
index bcd8cebb017..8350b2a8dc8 100644
--- a/src/services/formatting/formatting.ts
+++ b/src/services/formatting/formatting.ts
@@ -488,14 +488,16 @@ namespace ts.formatting {
// open and close brace, 'else' and 'while' (in do statement) tokens has indentation of the parent
case SyntaxKind.OpenBraceToken:
case SyntaxKind.CloseBraceToken:
- case SyntaxKind.OpenBracketToken:
- case SyntaxKind.CloseBracketToken:
case SyntaxKind.OpenParenToken:
case SyntaxKind.CloseParenToken:
case SyntaxKind.ElseKeyword:
case SyntaxKind.WhileKeyword:
case SyntaxKind.AtToken:
return indentation;
+ case SyntaxKind.OpenBracketToken:
+ case SyntaxKind.CloseBracketToken:
+ return (container.kind === SyntaxKind.MappedType) ?
+ indentation + getEffectiveDelta(delta, container) : indentation;
default:
// if token line equals to the line of containing node (this is a first token in the node) - use node indentation
return nodeStartLine !== line ? indentation + getEffectiveDelta(delta, container) : indentation;
@@ -566,7 +568,7 @@ namespace ts.formatting {
if (tokenInfo.token.end > node.end) {
break;
}
- consumeTokenAndAdvanceScanner(tokenInfo, node, nodeDynamicIndentation);
+ consumeTokenAndAdvanceScanner(tokenInfo, node, nodeDynamicIndentation, node);
}
function processChildNode(
@@ -617,7 +619,7 @@ namespace ts.formatting {
break;
}
- consumeTokenAndAdvanceScanner(tokenInfo, node, parentDynamicIndentation);
+ consumeTokenAndAdvanceScanner(tokenInfo, node, parentDynamicIndentation, node);
}
if (!formattingScanner.isOnToken()) {
@@ -673,11 +675,11 @@ namespace ts.formatting {
computeIndentation(tokenInfo.token, startLine, Constants.Unknown, parent, parentDynamicIndentation, parentStartLine);
listDynamicIndentation = getDynamicIndentation(parent, parentStartLine, indentation.indentation, indentation.delta);
- consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation);
+ consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation, parent);
}
else {
// consume any tokens that precede the list as child elements of 'node' using its indentation scope
- consumeTokenAndAdvanceScanner(tokenInfo, parent, parentDynamicIndentation);
+ consumeTokenAndAdvanceScanner(tokenInfo, parent, parentDynamicIndentation, parent);
}
}
}
@@ -697,13 +699,13 @@ namespace ts.formatting {
// without this check close paren will be interpreted as list end token for function expression which is wrong
if (tokenInfo.token.kind === listEndToken && rangeContainsRange(parent, tokenInfo.token)) {
// consume list end token
- consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation);
+ consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation, parent);
}
}
}
}
- function consumeTokenAndAdvanceScanner(currentTokenInfo: TokenInfo, parent: Node, dynamicIndentation: DynamicIndentation, container?: Node): void {
+ function consumeTokenAndAdvanceScanner(currentTokenInfo: TokenInfo, parent: Node, dynamicIndentation: DynamicIndentation, container: Node): void {
Debug.assert(rangeContainsRange(parent, currentTokenInfo.token));
const lastTriviaWasNewLine = formattingScanner.lastTrailingTriviaWasNewLine();
diff --git a/src/services/formatting/rules.ts b/src/services/formatting/rules.ts
index a6bec97ed53..db25ce0dcb8 100644
--- a/src/services/formatting/rules.ts
+++ b/src/services/formatting/rules.ts
@@ -362,7 +362,7 @@ namespace ts.formatting {
this.NoSpaceAfterModuleImport = new Rule(RuleDescriptor.create2(Shared.TokenRange.FromTokens([SyntaxKind.ModuleKeyword, SyntaxKind.RequireKeyword]), SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete));
// Add a space around certain TypeScript keywords
- this.SpaceAfterCertainTypeScriptKeywords = new Rule(RuleDescriptor.create4(Shared.TokenRange.FromTokens([SyntaxKind.AbstractKeyword, SyntaxKind.ClassKeyword, SyntaxKind.DeclareKeyword, SyntaxKind.DefaultKeyword, SyntaxKind.EnumKeyword, SyntaxKind.ExportKeyword, SyntaxKind.ExtendsKeyword, SyntaxKind.GetKeyword, SyntaxKind.ImplementsKeyword, SyntaxKind.ImportKeyword, SyntaxKind.InterfaceKeyword, SyntaxKind.ModuleKeyword, SyntaxKind.NamespaceKeyword, SyntaxKind.PrivateKeyword, SyntaxKind.PublicKeyword, SyntaxKind.ProtectedKeyword, SyntaxKind.ReadonlyKeyword, SyntaxKind.SetKeyword, SyntaxKind.StaticKeyword, SyntaxKind.TypeKeyword, SyntaxKind.FromKeyword]), Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Space));
+ this.SpaceAfterCertainTypeScriptKeywords = new Rule(RuleDescriptor.create4(Shared.TokenRange.FromTokens([SyntaxKind.AbstractKeyword, SyntaxKind.ClassKeyword, SyntaxKind.DeclareKeyword, SyntaxKind.DefaultKeyword, SyntaxKind.EnumKeyword, SyntaxKind.ExportKeyword, SyntaxKind.ExtendsKeyword, SyntaxKind.GetKeyword, SyntaxKind.ImplementsKeyword, SyntaxKind.ImportKeyword, SyntaxKind.InterfaceKeyword, SyntaxKind.ModuleKeyword, SyntaxKind.NamespaceKeyword, SyntaxKind.PrivateKeyword, SyntaxKind.PublicKeyword, SyntaxKind.ProtectedKeyword, SyntaxKind.ReadonlyKeyword, SyntaxKind.SetKeyword, SyntaxKind.StaticKeyword, SyntaxKind.TypeKeyword, SyntaxKind.FromKeyword, SyntaxKind.KeyOfKeyword]), Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Space));
this.SpaceBeforeCertainTypeScriptKeywords = new Rule(RuleDescriptor.create4(Shared.TokenRange.Any, Shared.TokenRange.FromTokens([SyntaxKind.ExtendsKeyword, SyntaxKind.ImplementsKeyword, SyntaxKind.FromKeyword])), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Space));
// Treat string literals in module names as identifiers, and add a space between the literal and the opening Brace braces, e.g.: module "m2" {
@@ -578,6 +578,8 @@ namespace ts.formatting {
return context.currentTokenSpan.kind === SyntaxKind.EqualsToken || context.nextTokenSpan.kind === SyntaxKind.EqualsToken;
// "in" keyword in for (let x in []) { }
case SyntaxKind.ForInStatement:
+ // "in" keyword in [P in keyof T]: T[P]
+ case SyntaxKind.TypeParameter:
return context.currentTokenSpan.kind === SyntaxKind.InKeyword || context.nextTokenSpan.kind === SyntaxKind.InKeyword;
// Technically, "of" is not a binary operator, but format it the same way as "in"
case SyntaxKind.ForOfStatement:
@@ -832,6 +834,7 @@ namespace ts.formatting {
switch (parent.kind) {
case SyntaxKind.TypeReference:
case SyntaxKind.TypeAssertionExpression:
+ case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
case SyntaxKind.InterfaceDeclaration:
diff --git a/src/services/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts
index 84f83a9c7db..eb9e1ba04c5 100644
--- a/src/services/formatting/smartIndenter.ts
+++ b/src/services/formatting/smartIndenter.ts
@@ -438,6 +438,7 @@ namespace ts.formatting {
case SyntaxKind.ModuleBlock:
case SyntaxKind.ObjectLiteralExpression:
case SyntaxKind.TypeLiteral:
+ case SyntaxKind.MappedType:
case SyntaxKind.TupleType:
case SyntaxKind.CaseBlock:
case SyntaxKind.DefaultClause:
diff --git a/tests/cases/fourslash/formattingMappedType.ts b/tests/cases/fourslash/formattingMappedType.ts
new file mode 100644
index 00000000000..a33d948a9c3
--- /dev/null
+++ b/tests/cases/fourslash/formattingMappedType.ts
@@ -0,0 +1,12 @@
+///
+
+/////*generic*/type t < T > = {
+/////*map*/ [ P in keyof T ] : T [ P ]
+////};
+
+
+format.document();
+goTo.marker("generic");
+verify.currentLineContentIs("type t = {");
+goTo.marker("map");
+verify.currentLineContentIs(" [P in keyof T]: T[P]");
\ No newline at end of file