diff --git a/src/services/formatting/rules.ts b/src/services/formatting/rules.ts
index f609edb050119..876efa3a9e184 100644
--- a/src/services/formatting/rules.ts
+++ b/src/services/formatting/rules.ts
@@ -227,11 +227,13 @@ namespace ts.formatting {
public SpaceBetweenTagAndTemplateString: Rule;
public NoSpaceBetweenTagAndTemplateString: Rule;
- // Union type
+ // Type operation
public SpaceBeforeBar: Rule;
public NoSpaceBeforeBar: Rule;
public SpaceAfterBar: Rule;
public NoSpaceAfterBar: Rule;
+ public SpaceBeforeAmpersand: Rule;
+ public SpaceAfterAmpersand: Rule;
constructor() {
///
@@ -272,7 +274,7 @@ namespace ts.formatting {
this.SpaceBeforeOpenBraceInFunction = new Rule(RuleDescriptor.create2(this.FunctionOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsFunctionDeclContext, Rules.IsBeforeBlockContext, Rules.IsNotFormatOnEnter, Rules.IsSameLineTokenOrBeforeMultilineBlockContext), RuleAction.Space), RuleFlags.CanDeleteNewLines);
// Place a space before open brace in a TypeScript declaration that has braces as children (class, module, enum, etc)
- this.TypeScriptOpenBraceLeftTokenRange = Shared.TokenRange.FromTokens([SyntaxKind.Identifier, SyntaxKind.MultiLineCommentTrivia]);
+ this.TypeScriptOpenBraceLeftTokenRange = Shared.TokenRange.FromTokens([SyntaxKind.Identifier, SyntaxKind.MultiLineCommentTrivia, SyntaxKind.ClassKeyword]);
this.SpaceBeforeOpenBraceInTypeScriptDeclWithBlock = new Rule(RuleDescriptor.create2(this.TypeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsTypeScriptDeclWithBlockContext, Rules.IsNotFormatOnEnter, Rules.IsSameLineTokenOrBeforeMultilineBlockContext), RuleAction.Space), RuleFlags.CanDeleteNewLines);
// Place a space before open brace in a control flow construct
@@ -394,12 +396,13 @@ namespace ts.formatting {
this.SpaceBetweenTagAndTemplateString = new Rule(RuleDescriptor.create3(SyntaxKind.Identifier, Shared.TokenRange.FromTokens([SyntaxKind.NoSubstitutionTemplateLiteral, SyntaxKind.TemplateHead])), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Space));
this.NoSpaceBetweenTagAndTemplateString = new Rule(RuleDescriptor.create3(SyntaxKind.Identifier, Shared.TokenRange.FromTokens([SyntaxKind.NoSubstitutionTemplateLiteral, SyntaxKind.TemplateHead])), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
- // union type
+ // type operation
this.SpaceBeforeBar = new Rule(RuleDescriptor.create3(SyntaxKind.BarToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Space));
this.NoSpaceBeforeBar = new Rule(RuleDescriptor.create3(SyntaxKind.BarToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
this.SpaceAfterBar = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.BarToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Space));
this.NoSpaceAfterBar = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.BarToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
-
+ this.SpaceBeforeAmpersand = new Rule(RuleDescriptor.create3(SyntaxKind.AmpersandToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Space));
+ this.SpaceAfterAmpersand = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.AmpersandToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Space));
// These rules are higher in priority than user-configurable rules.
this.HighPriorityCommonRules =
@@ -432,6 +435,7 @@ namespace ts.formatting {
this.SpaceAfterTypeKeyword, this.NoSpaceAfterTypeKeyword,
this.SpaceBetweenTagAndTemplateString, this.NoSpaceBetweenTagAndTemplateString,
this.SpaceBeforeBar, this.NoSpaceBeforeBar, this.SpaceAfterBar, this.NoSpaceAfterBar,
+ this.SpaceBeforeAmpersand, this.SpaceAfterAmpersand,
// TypeScript-specific rules
this.NoSpaceAfterConstructor, this.NoSpaceAfterModuleImport,
@@ -663,6 +667,7 @@ namespace ts.formatting {
static NodeIsTypeScriptDeclWithBlockContext(node: Node): boolean {
switch (node.kind) {
case SyntaxKind.ClassDeclaration:
+ case SyntaxKind.ClassExpression:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.TypeLiteral:
diff --git a/src/services/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts
index ebbf09e9feb46..9c19e32ab6ffe 100644
--- a/src/services/formatting/smartIndenter.ts
+++ b/src/services/formatting/smartIndenter.ts
@@ -406,6 +406,7 @@ namespace ts.formatting {
function nodeContentIsAlwaysIndented(kind: SyntaxKind): boolean {
switch (kind) {
case SyntaxKind.ClassDeclaration:
+ case SyntaxKind.ClassExpression:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.TypeAliasDeclaration:
@@ -436,7 +437,6 @@ namespace ts.formatting {
case SyntaxKind.Parameter:
case SyntaxKind.FunctionType:
case SyntaxKind.ConstructorType:
- case SyntaxKind.UnionType:
case SyntaxKind.ParenthesizedType:
case SyntaxKind.TaggedTemplateExpression:
case SyntaxKind.AwaitExpression:
diff --git a/src/services/services.ts b/src/services/services.ts
index 4358c9d58a957..b174f240875ae 100644
--- a/src/services/services.ts
+++ b/src/services/services.ts
@@ -1866,7 +1866,7 @@ namespace ts {
let sourceMapText: string;
// Create a compilerHost object to allow the compiler to read and write files
let compilerHost: CompilerHost = {
- getSourceFile: (fileName, target) => fileName === inputFileName ? sourceFile : undefined,
+ getSourceFile: (fileName, target) => fileName === normalizeSlashes(inputFileName) ? sourceFile : undefined,
writeFile: (name, text, writeByteOrderMark) => {
if (fileExtensionIs(name, ".map")) {
Debug.assert(sourceMapText === undefined, `Unexpected multiple source map outputs for the file '${name}'`);
diff --git a/src/services/utilities.ts b/src/services/utilities.ts
index d5ad93260cde7..8b77dcb953ba5 100644
--- a/src/services/utilities.ts
+++ b/src/services/utilities.ts
@@ -360,7 +360,7 @@ namespace ts {
return find(startNode || sourceFile);
function findRightmostToken(n: Node): Node {
- if (isToken(n)) {
+ if (isToken(n) || n.kind === SyntaxKind.JsxText) {
return n;
}
@@ -371,24 +371,35 @@ namespace ts {
}
function find(n: Node): Node {
- if (isToken(n)) {
+ if (isToken(n) || n.kind === SyntaxKind.JsxText) {
return n;
}
- let children = n.getChildren();
+ const children = n.getChildren();
for (let i = 0, len = children.length; i < len; i++) {
let child = children[i];
- if (nodeHasTokens(child)) {
- if (position <= child.end) {
- if (child.getStart(sourceFile) >= position) {
- // actual start of the node is past the position - previous token should be at the end of previous child
- let candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ i);
- return candidate && findRightmostToken(candidate)
- }
- else {
- // candidate should be in this node
- return find(child);
- }
+ // condition 'position < child.end' checks if child node end after the position
+ // in the example below this condition will be false for 'aaaa' and 'bbbb' and true for 'ccc'
+ // aaaa___bbbb___$__ccc
+ // after we found child node with end after the position we check if start of the node is after the position.
+ // if yes - then position is in the trivia and we need to look into the previous child to find the token in question.
+ // if no - position is in the node itself so we should recurse in it.
+ // NOTE: JsxText is a weird kind of node that can contain only whitespaces (since they are not counted as trivia).
+ // if this is the case - then we should assume that token in question is located in previous child.
+ if (position < child.end && (nodeHasTokens(child) || child.kind === SyntaxKind.JsxText)) {
+ const start = child.getStart(sourceFile);
+ const lookInPreviousChild =
+ (start >= position) || // cursor in the leading trivia
+ (child.kind === SyntaxKind.JsxText && start === child.end); // whitespace only JsxText
+
+ if (lookInPreviousChild) {
+ // actual start of the node is past the position - previous token should be at the end of previous child
+ let candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ i);
+ return candidate && findRightmostToken(candidate)
+ }
+ else {
+ // candidate should be in this node
+ return find(child);
}
}
}
diff --git a/tests/cases/fourslash/formatClassExpression.ts b/tests/cases/fourslash/formatClassExpression.ts
new file mode 100644
index 0000000000000..0a00203e77fc8
--- /dev/null
+++ b/tests/cases/fourslash/formatClassExpression.ts
@@ -0,0 +1,23 @@
+///