diff --git a/src/compiler/core.ts b/src/compiler/core.ts index d930bee47117f..e423e3889e473 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -329,6 +329,21 @@ namespace ts { return undefined; } + /** + * Like `forEach`, but iterates in reverse order. + */ + export function forEachRight(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { + if (array) { + for (let i = array.length - 1; i >= 0; i--) { + const result = callback(array[i], i); + if (result) { + return result; + } + } + } + return undefined; + } + /** Like `forEach`, but suitable for use with numbers and strings (which may be falsy). */ export function firstDefined(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined { if (array === undefined) { @@ -2159,8 +2174,19 @@ namespace ts { return (arg: T) => f(arg) && g(arg); } - export function or(f: (arg: T) => boolean, g: (arg: T) => boolean): (arg: T) => boolean { - return arg => f(arg) || g(arg); + export function or(...fs: ((arg: T) => boolean)[]): (arg: T) => boolean { + return arg => { + for (const f of fs) { + if (f(arg)) { + return true; + } + } + return false; + }; + } + + export function not(fn: (...args: T) => boolean): (...args: T) => boolean { + return (...args) => !fn(...args); } export function assertType(_: T): void { } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 1645b9339ae5f..952e6a65f9cfb 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3380,11 +3380,7 @@ namespace ts { }; } - export interface TrailingSemicolonDeferringWriter extends EmitTextWriter { - resetPendingTrailingSemicolon(): void; - } - - export function getTrailingSemicolonDeferringWriter(writer: EmitTextWriter): TrailingSemicolonDeferringWriter { + export function getTrailingSemicolonDeferringWriter(writer: EmitTextWriter): EmitTextWriter { let pendingTrailingSemicolon = false; function commitPendingTrailingSemicolon() { @@ -3451,20 +3447,6 @@ namespace ts { commitPendingTrailingSemicolon(); writer.decreaseIndent(); }, - resetPendingTrailingSemicolon() { - pendingTrailingSemicolon = false; - } - }; - } - - export function getTrailingSemicolonOmittingWriter(writer: EmitTextWriter): EmitTextWriter { - const deferringWriter = getTrailingSemicolonDeferringWriter(writer); - return { - ...deferringWriter, - writeLine() { - deferringWriter.resetPendingTrailingSemicolon(); - writer.writeLine(); - }, }; } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index cf01109bb9af5..5ae2976f051c8 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1683,7 +1683,7 @@ namespace FourSlash { if (this.enableFormatting) { const edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, offset, ch, this.formatCodeSettings); if (edits.length) { - offset += this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true); + offset += this.applyEdits(this.activeFile.fileName, edits); } } } @@ -1756,7 +1756,7 @@ namespace FourSlash { if (this.enableFormatting) { const edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, offset, ch, this.formatCodeSettings); if (edits.length) { - offset += this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true); + offset += this.applyEdits(this.activeFile.fileName, edits); } } } @@ -1775,7 +1775,7 @@ namespace FourSlash { if (this.enableFormatting) { const edits = this.languageService.getFormattingEditsForRange(this.activeFile.fileName, start, offset, this.formatCodeSettings); if (edits.length) { - this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true); + this.applyEdits(this.activeFile.fileName, edits); } } @@ -1810,9 +1810,7 @@ namespace FourSlash { * @returns The number of characters added to the file as a result of the edits. * May be negative. */ - private applyEdits(fileName: string, edits: readonly ts.TextChange[], isFormattingEdit: boolean): number { - // Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters - const oldContent = this.getFileContent(fileName); + private applyEdits(fileName: string, edits: readonly ts.TextChange[]): number { let runningOffset = 0; forEachTextChange(edits, edit => { @@ -1833,14 +1831,6 @@ namespace FourSlash { runningOffset += editDelta; }); - if (isFormattingEdit) { - const newContent = this.getFileContent(fileName); - - if (this.removeWhitespace(newContent) !== this.removeWhitespace(oldContent)) { - this.raiseError("Formatting operation destroyed non-whitespace content"); - } - } - return runningOffset; } @@ -1856,17 +1846,17 @@ namespace FourSlash { public formatDocument() { const edits = this.languageService.getFormattingEditsForDocument(this.activeFile.fileName, this.formatCodeSettings); - this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true); + this.applyEdits(this.activeFile.fileName, edits); } public formatSelection(start: number, end: number) { const edits = this.languageService.getFormattingEditsForRange(this.activeFile.fileName, start, end, this.formatCodeSettings); - this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true); + this.applyEdits(this.activeFile.fileName, edits); } public formatOnType(pos: number, key: string) { const edits = this.languageService.getFormattingEditsAfterKeystroke(this.activeFile.fileName, pos, key, this.formatCodeSettings); - this.applyEdits(this.activeFile.fileName, edits, /*isFormattingEdit*/ true); + this.applyEdits(this.activeFile.fileName, edits); } private editScriptAndUpdateMarkers(fileName: string, editStart: number, editEnd: number, newText: string) { @@ -2414,7 +2404,7 @@ namespace FourSlash { if (options.applyChanges) { for (const change of action.changes) { - this.applyEdits(change.fileName, change.textChanges, /*isFormattingEdit*/ false); + this.applyEdits(change.fileName, change.textChanges); } this.verifyNewContentAfterChange(options, action.changes.map(c => c.fileName)); } @@ -2497,7 +2487,7 @@ namespace FourSlash { private applyChanges(changes: readonly ts.FileTextChanges[]): void { for (const change of changes) { - this.applyEdits(change.fileName, change.textChanges, /*isFormattingEdit*/ false); + this.applyEdits(change.fileName, change.textChanges); } } @@ -2525,7 +2515,7 @@ namespace FourSlash { ts.Debug.assert(codeFix.changes.length === 1); const change = ts.first(codeFix.changes); ts.Debug.assert(change.fileName === fileName); - this.applyEdits(change.fileName, change.textChanges, /*isFormattingEdit*/ false); + this.applyEdits(change.fileName, change.textChanges); const text = range ? this.rangeText(range) : this.getFileContent(this.activeFile.fileName); actualTextArray.push(text); scriptInfo.updateContent(originalContent); @@ -2929,7 +2919,7 @@ namespace FourSlash { const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, this.formatCodeSettings, range, refactorName, actionName, ts.emptyOptions)!; for (const edit of editInfo.edits) { - this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false); + this.applyEdits(edit.fileName, edit.textChanges); } let renameFilename: string | undefined; @@ -3045,7 +3035,7 @@ namespace FourSlash { const editInfo = this.languageService.getEditsForRefactor(marker.fileName, formattingOptions, marker.position, refactorNameToApply, actionName, ts.emptyOptions)!; for (const edit of editInfo.edits) { - this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false); + this.applyEdits(edit.fileName, edit.textChanges); } const actualContent = this.getFileContent(marker.fileName); diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 0674f1c142de3..0ec27a3a2bb38 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -2956,6 +2956,12 @@ namespace ts.server.protocol { Smart = "Smart", } + export enum SemicolonPreference { + Ignore = "ignore", + Insert = "insert", + Remove = "remove", + } + export interface EditorSettings { baseIndentSize?: number; indentSize?: number; @@ -2982,6 +2988,7 @@ namespace ts.server.protocol { placeOpenBraceOnNewLineForFunctions?: boolean; placeOpenBraceOnNewLineForControlBlocks?: boolean; insertSpaceBeforeTypeAnnotation?: boolean; + semicolons?: SemicolonPreference; } export interface UserPreferences { diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 4ae8b3466fb05..7dfb14661563a 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -169,7 +169,7 @@ namespace ts.codefix { // We sort the best codefixes first, so taking `first` is best for completions. const moduleSpecifier = first(getNewImportInfos(program, sourceFile, position, exportInfos, host, preferences)).moduleSpecifier; const fix = first(getFixForImport(exportInfos, symbolName, position, program, sourceFile, host, preferences)); - return { moduleSpecifier, codeAction: codeFixActionToCodeAction(codeActionForFix({ host, formatContext }, sourceFile, symbolName, fix, getQuotePreference(sourceFile, preferences))) }; + return { moduleSpecifier, codeAction: codeFixActionToCodeAction(codeActionForFix({ host, formatContext, preferences }, sourceFile, symbolName, fix, getQuotePreference(sourceFile, preferences))) }; } function codeFixActionToCodeAction({ description, changes, commands }: CodeFixAction): CodeAction { diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index f9b0d752db706..6895ddc96b9b7 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -2,16 +2,14 @@ namespace ts.formatting { export interface FormatContext { readonly options: FormatCodeSettings; - readonly getRule: RulesMap; + readonly getRules: RulesMap; } - export interface TextRangeWithKind extends TextRange { - kind: SyntaxKind; + export interface TextRangeWithKind extends TextRange { + kind: T; } - export interface TextRangeWithTriviaKind extends TextRange { - kind: TriviaKind; - } + export type TextRangeWithTriviaKind = TextRangeWithKind; export interface TokenInfo { leadingTrivia: TextRangeWithTriviaKind[] | undefined; @@ -19,6 +17,16 @@ namespace ts.formatting { trailingTrivia: TextRangeWithTriviaKind[] | undefined; } + export function createTextRangeWithKind(pos: number, end: number, kind: T): TextRangeWithKind { + const textRangeWithKind: TextRangeWithKind = { pos, end, kind }; + if (Debug.isDebugging) { + Object.defineProperty(textRangeWithKind, "__debugKind", { + get: () => Debug.formatSyntaxKind(kind), + }); + } + return textRangeWithKind; + } + const enum Constants { Unknown = -1 } @@ -386,7 +394,7 @@ namespace ts.formatting { initialIndentation: number, delta: number, formattingScanner: FormattingScanner, - { options, getRule }: FormatContext, + { options, getRules }: FormatContext, requestKind: FormattingRequestKind, rangeContainsError: (r: TextRange) => boolean, sourceFile: SourceFileLike): TextChange[] { @@ -469,7 +477,7 @@ namespace ts.formatting { parent: Node, parentDynamicIndentation: DynamicIndentation, effectiveParentStartLine: number - ): { indentation: number, delta: number } { + ): { indentation: number, delta: number; } { const delta = SmartIndenter.shouldIndentChildNode(options, node) ? options.indentSize! : 0; if (effectiveParentStartLine === startLine) { @@ -644,6 +652,21 @@ namespace ts.formatting { consumeTokenAndAdvanceScanner(tokenInfo, node, nodeDynamicIndentation, node); } + if (!node.parent && formattingScanner.isOnEOF()) { + const token = formattingScanner.readEOFTokenRange(); + if (token.end <= node.end && previousRange) { + processPair( + token, + sourceFile.getLineAndCharacterOfPosition(token.pos).line, + node, + previousRange, + previousRangeStartLine, + previousParent, + contextNode, + nodeDynamicIndentation); + } + } + function processChildNode( child: Node, inheritedIndentation: number, @@ -938,37 +961,41 @@ namespace ts.formatting { formattingContext.updateContext(previousItem, previousParent, currentItem, currentParent, contextNode); - const rule = getRule(formattingContext); + const rules = getRules(formattingContext); - let trimTrailingWhitespaces: boolean; + let trimTrailingWhitespaces = false; let lineAction = LineAction.None; - if (rule) { - lineAction = applyRuleEdits(rule, previousItem, previousStartLine, currentItem, currentStartLine); - switch (lineAction) { - case LineAction.LineRemoved: - // Handle the case where the next line is moved to be the end of this line. - // In this case we don't indent the next line in the next pass. - if (currentParent.getStart(sourceFile) === currentItem.pos) { - dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ false); - } - break; - case LineAction.LineAdded: - // Handle the case where token2 is moved to the new line. - // In this case we indent token2 in the next pass but we set - // sameLineIndent flag to notify the indenter that the indentation is within the line. - if (currentParent.getStart(sourceFile) === currentItem.pos) { - dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ true); - } - break; - default: - Debug.assert(lineAction === LineAction.None); - } + if (rules) { + // Apply rules in reverse order so that higher priority rules (which are first in the array) + // win in a conflict with lower priority rules. + forEachRight(rules, rule => { + lineAction = applyRuleEdits(rule, previousItem, previousStartLine, currentItem, currentStartLine); + switch (lineAction) { + case LineAction.LineRemoved: + // Handle the case where the next line is moved to be the end of this line. + // In this case we don't indent the next line in the next pass. + if (currentParent.getStart(sourceFile) === currentItem.pos) { + dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ false); + } + break; + case LineAction.LineAdded: + // Handle the case where token2 is moved to the new line. + // In this case we indent token2 in the next pass but we set + // sameLineIndent flag to notify the indenter that the indentation is within the line. + if (currentParent.getStart(sourceFile) === currentItem.pos) { + dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ true); + } + break; + default: + Debug.assert(lineAction === LineAction.None); + } - // We need to trim trailing whitespace between the tokens if they were on different lines, and no rule was applied to put them on the same line - trimTrailingWhitespaces = !(rule.action & RuleAction.Delete) && rule.flags !== RuleFlags.CanDeleteNewLines; + // We need to trim trailing whitespace between the tokens if they were on different lines, and no rule was applied to put them on the same line + trimTrailingWhitespaces = !(rule.action & RuleAction.DeleteSpace) && rule.flags !== RuleFlags.CanDeleteNewLines; + }); } else { - trimTrailingWhitespaces = true; + trimTrailingWhitespaces = currentItem.kind !== SyntaxKind.EndOfFileToken; } if (currentStartLine !== previousStartLine && trimTrailingWhitespaces) { @@ -1130,6 +1157,12 @@ namespace ts.formatting { } } + function recordInsert(start: number, text: string) { + if (text) { + edits.push(createTextChangeFromStartLength(start, 0, text)); + } + } + function applyRuleEdits(rule: Rule, previousRange: TextRangeWithKind, previousStartLine: number, @@ -1138,17 +1171,20 @@ namespace ts.formatting { ): LineAction { const onLaterLine = currentStartLine !== previousStartLine; switch (rule.action) { - case RuleAction.Ignore: + case RuleAction.StopProcessingSpaceActions: // no action required return LineAction.None; - case RuleAction.Delete: + case RuleAction.DeleteSpace: if (previousRange.end !== currentRange.pos) { // delete characters starting from t1.end up to t2.pos exclusive recordDelete(previousRange.end, currentRange.pos - previousRange.end); return onLaterLine ? LineAction.LineRemoved : LineAction.None; } break; - case RuleAction.NewLine: + case RuleAction.DeleteToken: + recordDelete(previousRange.pos, previousRange.end - previousRange.pos); + break; + case RuleAction.InsertNewLine: // exit early if we on different lines and rule cannot change number of newlines // if line1 and line2 are on subsequent lines then no edits are required - ok to exit // if line1 and line2 are separated with more than one newline - ok to exit since we cannot delete extra new lines @@ -1163,7 +1199,7 @@ namespace ts.formatting { return onLaterLine ? LineAction.None : LineAction.LineAdded; } break; - case RuleAction.Space: + case RuleAction.InsertSpace: // exit early if we on different lines and rule cannot change number of newlines if (rule.flags !== RuleFlags.CanDeleteNewLines && previousStartLine !== currentStartLine) { return LineAction.None; @@ -1174,6 +1210,9 @@ namespace ts.formatting { recordReplace(previousRange.end, currentRange.pos - previousRange.end, " "); return onLaterLine ? LineAction.LineRemoved : LineAction.None; } + break; + case RuleAction.InsertTrailingSemicolon: + recordInsert(previousRange.end, ";"); } return LineAction.None; } @@ -1271,7 +1310,7 @@ namespace ts.formatting { return SyntaxKind.Unknown; } - let internedSizes: { tabSize: number; indentSize: number }; + let internedSizes: { tabSize: number; indentSize: number; }; let internedTabsIndentation: string[] | undefined; let internedSpacesIndentation: string[] | undefined; diff --git a/src/services/formatting/formattingContext.ts b/src/services/formatting/formattingContext.ts index e5a1ff5d4ac8f..df479acdf4e35 100644 --- a/src/services/formatting/formattingContext.ts +++ b/src/services/formatting/formattingContext.ts @@ -99,4 +99,4 @@ namespace ts.formatting { return false; } } -} \ No newline at end of file +} diff --git a/src/services/formatting/formattingScanner.ts b/src/services/formatting/formattingScanner.ts index c1e97e636d093..3e6c2a9c9ff5c 100644 --- a/src/services/formatting/formattingScanner.ts +++ b/src/services/formatting/formattingScanner.ts @@ -6,7 +6,9 @@ namespace ts.formatting { export interface FormattingScanner { advance(): void; isOnToken(): boolean; + isOnEOF(): boolean; readTokenInfo(n: Node): TokenInfo; + readEOFTokenRange(): TextRangeWithKind; getCurrentLeadingTrivia(): TextRangeWithKind[] | undefined; lastTrailingTriviaWasNewLine(): boolean; skipToEndOf(node: Node): void; @@ -38,7 +40,9 @@ namespace ts.formatting { const res = cb({ advance, readTokenInfo, + readEOFTokenRange, isOnToken, + isOnEOF, getCurrentLeadingTrivia: () => leadingTrivia, lastTrailingTriviaWasNewLine: () => wasNewLine, skipToEndOf, @@ -164,11 +168,11 @@ namespace ts.formatting { let currentToken = getNextToken(n, expectedScanAction); - const token: TextRangeWithKind = { - pos: scanner.getStartPos(), - end: scanner.getTextPos(), - kind: currentToken - }; + const token = createTextRangeWithKind( + scanner.getStartPos(), + scanner.getTextPos(), + currentToken, + ); // consume trailing trivia if (trailingTrivia) { @@ -179,11 +183,11 @@ namespace ts.formatting { if (!isTrivia(currentToken)) { break; } - const trivia: TextRangeWithTriviaKind = { - pos: scanner.getStartPos(), - end: scanner.getTextPos(), - kind: currentToken - }; + const trivia = createTextRangeWithKind( + scanner.getStartPos(), + scanner.getTextPos(), + currentToken, + ); if (!trailingTrivia) { trailingTrivia = []; @@ -243,12 +247,22 @@ namespace ts.formatting { return token; } + function readEOFTokenRange(): TextRangeWithKind { + Debug.assert(isOnEOF()); + return createTextRangeWithKind(scanner.getStartPos(), scanner.getTextPos(), SyntaxKind.EndOfFileToken); + } + function isOnToken(): boolean { const current = lastTokenInfo ? lastTokenInfo.token.kind : scanner.getToken(); const startPos = lastTokenInfo ? lastTokenInfo.token.pos : scanner.getStartPos(); return startPos < endPos && current !== SyntaxKind.EndOfFileToken && !isTrivia(current); } + function isOnEOF(): boolean { + const current = lastTokenInfo ? lastTokenInfo.token.kind : scanner.getToken(); + return current === SyntaxKind.EndOfFileToken; + } + // when containing node in the tree is token // but its kind differs from the kind that was returned by the scanner, // then kind needs to be fixed. This might happen in cases diff --git a/src/services/formatting/rule.ts b/src/services/formatting/rule.ts index a35489523241c..ef98461738891 100644 --- a/src/services/formatting/rule.ts +++ b/src/services/formatting/rule.ts @@ -12,10 +12,17 @@ namespace ts.formatting { export const anyContext: readonly ContextPredicate[] = emptyArray; export const enum RuleAction { - Ignore = 1 << 0, - Space = 1 << 1, - NewLine = 1 << 2, - Delete = 1 << 3, + StopProcessingSpaceActions = 1 << 0, + StopProcessingTokenActions = 1 << 1, + InsertSpace = 1 << 2, + InsertNewLine = 1 << 3, + DeleteSpace = 1 << 4, + DeleteToken = 1 << 5, + InsertTrailingSemicolon = 1 << 6, + + StopAction = StopProcessingSpaceActions | StopProcessingTokenActions, + ModifySpaceAction = InsertSpace | InsertNewLine | DeleteSpace, + ModifyTokenAction = DeleteToken | InsertTrailingSemicolon, } export const enum RuleFlags { @@ -27,4 +34,4 @@ namespace ts.formatting { readonly tokens: readonly SyntaxKind[]; readonly isSpecific: boolean; } -} \ No newline at end of file +} diff --git a/src/services/formatting/rules.ts b/src/services/formatting/rules.ts index 670048eb737a8..75720555f5248 100644 --- a/src/services/formatting/rules.ts +++ b/src/services/formatting/rules.ts @@ -9,7 +9,9 @@ namespace ts.formatting { export function getAllRules(): RuleSpec[] { const allTokens: SyntaxKind[] = []; for (let token = SyntaxKind.FirstToken; token <= SyntaxKind.LastToken; token++) { - allTokens.push(token); + if (token !== SyntaxKind.EndOfFileToken) { + allTokens.push(token); + } } function anyTokenExcept(...tokens: SyntaxKind[]): TokenRange { return { tokens: allTokens.filter(t => !tokens.some(t2 => t2 === t)), isSpecific: false }; @@ -17,6 +19,7 @@ namespace ts.formatting { const anyToken: TokenRange = { tokens: allTokens, isSpecific: false }; const anyTokenIncludingMultilineComments = tokenRangeFrom([...allTokens, SyntaxKind.MultiLineCommentTrivia]); + const anyTokenIncludingEOF = tokenRangeFrom([...allTokens, SyntaxKind.EndOfFileToken]); const keywords = tokenRangeFromRange(SyntaxKind.FirstKeyword, SyntaxKind.LastKeyword); const binaryOperators = tokenRangeFromRange(SyntaxKind.FirstBinaryOperator, SyntaxKind.LastBinaryOperator); const binaryKeywordOperators = [SyntaxKind.InKeyword, SyntaxKind.InstanceOfKeyword, SyntaxKind.OfKeyword, SyntaxKind.AsKeyword, SyntaxKind.IsKeyword]; @@ -44,103 +47,103 @@ namespace ts.formatting { // These rules are higher in priority than user-configurable const highPriorityCommonRules = [ // Leave comments alone - rule("IgnoreBeforeComment", anyToken, comments, anyContext, RuleAction.Ignore), - rule("IgnoreAfterLineComment", SyntaxKind.SingleLineCommentTrivia, anyToken, anyContext, RuleAction.Ignore), + rule("IgnoreBeforeComment", anyToken, comments, anyContext, RuleAction.StopProcessingSpaceActions), + rule("IgnoreAfterLineComment", SyntaxKind.SingleLineCommentTrivia, anyToken, anyContext, RuleAction.StopProcessingSpaceActions), - rule("NotSpaceBeforeColon", anyToken, SyntaxKind.ColonToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext, isNotTypeAnnotationContext], RuleAction.Delete), - rule("SpaceAfterColon", SyntaxKind.ColonToken, anyToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.Space), - rule("NoSpaceBeforeQuestionMark", anyToken, SyntaxKind.QuestionToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.Delete), + rule("NotSpaceBeforeColon", anyToken, SyntaxKind.ColonToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext, isNotTypeAnnotationContext], RuleAction.DeleteSpace), + rule("SpaceAfterColon", SyntaxKind.ColonToken, anyToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.InsertSpace), + rule("NoSpaceBeforeQuestionMark", anyToken, SyntaxKind.QuestionToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.DeleteSpace), // insert space after '?' only when it is used in conditional operator - rule("SpaceAfterQuestionMarkInConditionalOperator", SyntaxKind.QuestionToken, anyToken, [isNonJsxSameLineTokenContext, isConditionalOperatorContext], RuleAction.Space), + rule("SpaceAfterQuestionMarkInConditionalOperator", SyntaxKind.QuestionToken, anyToken, [isNonJsxSameLineTokenContext, isConditionalOperatorContext], RuleAction.InsertSpace), // in other cases there should be no space between '?' and next token - rule("NoSpaceAfterQuestionMark", SyntaxKind.QuestionToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), + rule("NoSpaceAfterQuestionMark", SyntaxKind.QuestionToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceBeforeDot", anyToken, SyntaxKind.DotToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), - rule("NoSpaceAfterDot", SyntaxKind.DotToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), + rule("NoSpaceBeforeDot", anyToken, SyntaxKind.DotToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterDot", SyntaxKind.DotToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("NoSpaceBetweenImportParenInImportType", SyntaxKind.ImportKeyword, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isImportTypeContext], RuleAction.Delete), + rule("NoSpaceBetweenImportParenInImportType", SyntaxKind.ImportKeyword, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isImportTypeContext], RuleAction.DeleteSpace), // Special handling of unary operators. // Prefix operators generally shouldn't have a space between // them and their target unary expression. - rule("NoSpaceAfterUnaryPrefixOperator", unaryPrefixOperators, unaryPrefixExpressions, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.Delete), - rule("NoSpaceAfterUnaryPreincrementOperator", SyntaxKind.PlusPlusToken, unaryPreincrementExpressions, [isNonJsxSameLineTokenContext], RuleAction.Delete), - rule("NoSpaceAfterUnaryPredecrementOperator", SyntaxKind.MinusMinusToken, unaryPredecrementExpressions, [isNonJsxSameLineTokenContext], RuleAction.Delete), - rule("NoSpaceBeforeUnaryPostincrementOperator", unaryPostincrementExpressions, SyntaxKind.PlusPlusToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), - rule("NoSpaceBeforeUnaryPostdecrementOperator", unaryPostdecrementExpressions, SyntaxKind.MinusMinusToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), + rule("NoSpaceAfterUnaryPrefixOperator", unaryPrefixOperators, unaryPrefixExpressions, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterUnaryPreincrementOperator", SyntaxKind.PlusPlusToken, unaryPreincrementExpressions, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterUnaryPredecrementOperator", SyntaxKind.MinusMinusToken, unaryPredecrementExpressions, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeUnaryPostincrementOperator", unaryPostincrementExpressions, SyntaxKind.PlusPlusToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeUnaryPostdecrementOperator", unaryPostdecrementExpressions, SyntaxKind.MinusMinusToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), // More unary operator special-casing. // DevDiv 181814: Be careful when removing leading whitespace // around unary operators. Examples: // 1 - -2 --X--> 1--2 // a + ++b --X--> a+++b - rule("SpaceAfterPostincrementWhenFollowedByAdd", SyntaxKind.PlusPlusToken, SyntaxKind.PlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Space), - rule("SpaceAfterAddWhenFollowedByUnaryPlus", SyntaxKind.PlusToken, SyntaxKind.PlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Space), - rule("SpaceAfterAddWhenFollowedByPreincrement", SyntaxKind.PlusToken, SyntaxKind.PlusPlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Space), - rule("SpaceAfterPostdecrementWhenFollowedBySubtract", SyntaxKind.MinusMinusToken, SyntaxKind.MinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Space), - rule("SpaceAfterSubtractWhenFollowedByUnaryMinus", SyntaxKind.MinusToken, SyntaxKind.MinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Space), - rule("SpaceAfterSubtractWhenFollowedByPredecrement", SyntaxKind.MinusToken, SyntaxKind.MinusMinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Space), - - rule("NoSpaceAfterCloseBrace", SyntaxKind.CloseBraceToken, [SyntaxKind.CommaToken, SyntaxKind.SemicolonToken], [isNonJsxSameLineTokenContext], RuleAction.Delete), + rule("SpaceAfterPostincrementWhenFollowedByAdd", SyntaxKind.PlusPlusToken, SyntaxKind.PlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterAddWhenFollowedByUnaryPlus", SyntaxKind.PlusToken, SyntaxKind.PlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterAddWhenFollowedByPreincrement", SyntaxKind.PlusToken, SyntaxKind.PlusPlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterPostdecrementWhenFollowedBySubtract", SyntaxKind.MinusMinusToken, SyntaxKind.MinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterSubtractWhenFollowedByUnaryMinus", SyntaxKind.MinusToken, SyntaxKind.MinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterSubtractWhenFollowedByPredecrement", SyntaxKind.MinusToken, SyntaxKind.MinusMinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + + rule("NoSpaceAfterCloseBrace", SyntaxKind.CloseBraceToken, [SyntaxKind.CommaToken, SyntaxKind.SemicolonToken], [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), // For functions and control block place } on a new line [multi-line rule] - rule("NewLineBeforeCloseBraceInBlockContext", anyTokenIncludingMultilineComments, SyntaxKind.CloseBraceToken, [isMultilineBlockContext], RuleAction.NewLine), + rule("NewLineBeforeCloseBraceInBlockContext", anyTokenIncludingMultilineComments, SyntaxKind.CloseBraceToken, [isMultilineBlockContext], RuleAction.InsertNewLine), // Space/new line after }. - rule("SpaceAfterCloseBrace", SyntaxKind.CloseBraceToken, anyTokenExcept(SyntaxKind.CloseParenToken), [isNonJsxSameLineTokenContext, isAfterCodeBlockContext], RuleAction.Space), + rule("SpaceAfterCloseBrace", SyntaxKind.CloseBraceToken, anyTokenExcept(SyntaxKind.CloseParenToken), [isNonJsxSameLineTokenContext, isAfterCodeBlockContext], RuleAction.InsertSpace), // Special case for (}, else) and (}, while) since else & while tokens are not part of the tree which makes SpaceAfterCloseBrace rule not applied // Also should not apply to }) - rule("SpaceBetweenCloseBraceAndElse", SyntaxKind.CloseBraceToken, SyntaxKind.ElseKeyword, [isNonJsxSameLineTokenContext], RuleAction.Space), - rule("SpaceBetweenCloseBraceAndWhile", SyntaxKind.CloseBraceToken, SyntaxKind.WhileKeyword, [isNonJsxSameLineTokenContext], RuleAction.Space), - rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectContext], RuleAction.Delete), + rule("SpaceBetweenCloseBraceAndElse", SyntaxKind.CloseBraceToken, SyntaxKind.ElseKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBetweenCloseBraceAndWhile", SyntaxKind.CloseBraceToken, SyntaxKind.WhileKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectContext], RuleAction.DeleteSpace), // Add a space after control dec context if the next character is an open bracket ex: 'if (false)[a, b] = [1, 2];' -> 'if (false) [a, b] = [1, 2];' - rule("SpaceAfterConditionalClosingParen", SyntaxKind.CloseParenToken, SyntaxKind.OpenBracketToken, [isControlDeclContext], RuleAction.Space), + rule("SpaceAfterConditionalClosingParen", SyntaxKind.CloseParenToken, SyntaxKind.OpenBracketToken, [isControlDeclContext], RuleAction.InsertSpace), - rule("NoSpaceBetweenFunctionKeywordAndStar", SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken, [isFunctionDeclarationOrFunctionExpressionContext], RuleAction.Delete), - rule("SpaceAfterStarInGeneratorDeclaration", SyntaxKind.AsteriskToken, SyntaxKind.Identifier, [isFunctionDeclarationOrFunctionExpressionContext], RuleAction.Space), + rule("NoSpaceBetweenFunctionKeywordAndStar", SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken, [isFunctionDeclarationOrFunctionExpressionContext], RuleAction.DeleteSpace), + rule("SpaceAfterStarInGeneratorDeclaration", SyntaxKind.AsteriskToken, SyntaxKind.Identifier, [isFunctionDeclarationOrFunctionExpressionContext], RuleAction.InsertSpace), - rule("SpaceAfterFunctionInFuncDecl", SyntaxKind.FunctionKeyword, anyToken, [isFunctionDeclContext], RuleAction.Space), + rule("SpaceAfterFunctionInFuncDecl", SyntaxKind.FunctionKeyword, anyToken, [isFunctionDeclContext], RuleAction.InsertSpace), // Insert new line after { and before } in multi-line contexts. - rule("NewLineAfterOpenBraceInBlockContext", SyntaxKind.OpenBraceToken, anyToken, [isMultilineBlockContext], RuleAction.NewLine), + rule("NewLineAfterOpenBraceInBlockContext", SyntaxKind.OpenBraceToken, anyToken, [isMultilineBlockContext], RuleAction.InsertNewLine), // For get/set members, we check for (identifier,identifier) since get/set don't have tokens and they are represented as just an identifier token. // Though, we do extra check on the context to make sure we are dealing with get/set node. Example: // get x() {} // set x(val) {} - rule("SpaceAfterGetSetInMember", [SyntaxKind.GetKeyword, SyntaxKind.SetKeyword], SyntaxKind.Identifier, [isFunctionDeclContext], RuleAction.Space), + rule("SpaceAfterGetSetInMember", [SyntaxKind.GetKeyword, SyntaxKind.SetKeyword], SyntaxKind.Identifier, [isFunctionDeclContext], RuleAction.InsertSpace), - rule("NoSpaceBetweenYieldKeywordAndStar", SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken, [isNonJsxSameLineTokenContext, isYieldOrYieldStarWithOperand], RuleAction.Delete), - rule("SpaceBetweenYieldOrYieldStarAndOperand", [SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken], anyToken, [isNonJsxSameLineTokenContext, isYieldOrYieldStarWithOperand], RuleAction.Space), + rule("NoSpaceBetweenYieldKeywordAndStar", SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken, [isNonJsxSameLineTokenContext, isYieldOrYieldStarWithOperand], RuleAction.DeleteSpace), + rule("SpaceBetweenYieldOrYieldStarAndOperand", [SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken], anyToken, [isNonJsxSameLineTokenContext, isYieldOrYieldStarWithOperand], RuleAction.InsertSpace), - rule("NoSpaceBetweenReturnAndSemicolon", SyntaxKind.ReturnKeyword, SyntaxKind.SemicolonToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), - rule("SpaceAfterCertainKeywords", [SyntaxKind.VarKeyword, SyntaxKind.ThrowKeyword, SyntaxKind.NewKeyword, SyntaxKind.DeleteKeyword, SyntaxKind.ReturnKeyword, SyntaxKind.TypeOfKeyword, SyntaxKind.AwaitKeyword], anyToken, [isNonJsxSameLineTokenContext], RuleAction.Space), - rule("SpaceAfterLetConstInVariableDeclaration", [SyntaxKind.LetKeyword, SyntaxKind.ConstKeyword], anyToken, [isNonJsxSameLineTokenContext, isStartOfVariableDeclarationList], RuleAction.Space), - rule("NoSpaceBeforeOpenParenInFuncCall", anyToken, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isFunctionCallOrNewContext, isPreviousTokenNotComma], RuleAction.Delete), + rule("NoSpaceBetweenReturnAndSemicolon", SyntaxKind.ReturnKeyword, SyntaxKind.SemicolonToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("SpaceAfterCertainKeywords", [SyntaxKind.VarKeyword, SyntaxKind.ThrowKeyword, SyntaxKind.NewKeyword, SyntaxKind.DeleteKeyword, SyntaxKind.ReturnKeyword, SyntaxKind.TypeOfKeyword, SyntaxKind.AwaitKeyword], anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceAfterLetConstInVariableDeclaration", [SyntaxKind.LetKeyword, SyntaxKind.ConstKeyword], anyToken, [isNonJsxSameLineTokenContext, isStartOfVariableDeclarationList], RuleAction.InsertSpace), + rule("NoSpaceBeforeOpenParenInFuncCall", anyToken, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isFunctionCallOrNewContext, isPreviousTokenNotComma], RuleAction.DeleteSpace), // Special case for binary operators (that are keywords). For these we have to add a space and shouldn't follow any user options. - rule("SpaceBeforeBinaryKeywordOperator", anyToken, binaryKeywordOperators, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Space), - rule("SpaceAfterBinaryKeywordOperator", binaryKeywordOperators, anyToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Space), + rule("SpaceBeforeBinaryKeywordOperator", anyToken, binaryKeywordOperators, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterBinaryKeywordOperator", binaryKeywordOperators, anyToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), - rule("SpaceAfterVoidOperator", SyntaxKind.VoidKeyword, anyToken, [isNonJsxSameLineTokenContext, isVoidOpContext], RuleAction.Space), + rule("SpaceAfterVoidOperator", SyntaxKind.VoidKeyword, anyToken, [isNonJsxSameLineTokenContext, isVoidOpContext], RuleAction.InsertSpace), // Async-await - rule("SpaceBetweenAsyncAndOpenParen", SyntaxKind.AsyncKeyword, SyntaxKind.OpenParenToken, [isArrowFunctionContext, isNonJsxSameLineTokenContext], RuleAction.Space), - rule("SpaceBetweenAsyncAndFunctionKeyword", SyntaxKind.AsyncKeyword, SyntaxKind.FunctionKeyword, [isNonJsxSameLineTokenContext], RuleAction.Space), + rule("SpaceBetweenAsyncAndOpenParen", SyntaxKind.AsyncKeyword, SyntaxKind.OpenParenToken, [isArrowFunctionContext, isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBetweenAsyncAndFunctionKeyword", SyntaxKind.AsyncKeyword, SyntaxKind.FunctionKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), // Template string - rule("NoSpaceBetweenTagAndTemplateString", [SyntaxKind.Identifier, SyntaxKind.CloseParenToken], [SyntaxKind.NoSubstitutionTemplateLiteral, SyntaxKind.TemplateHead], [isNonJsxSameLineTokenContext], RuleAction.Delete), + rule("NoSpaceBetweenTagAndTemplateString", [SyntaxKind.Identifier, SyntaxKind.CloseParenToken], [SyntaxKind.NoSubstitutionTemplateLiteral, SyntaxKind.TemplateHead], [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), // JSX opening elements - rule("SpaceBeforeJsxAttribute", anyToken, SyntaxKind.Identifier, [isNextTokenParentJsxAttribute, isNonJsxSameLineTokenContext], RuleAction.Space), - rule("SpaceBeforeSlashInJsxOpeningElement", anyToken, SyntaxKind.SlashToken, [isJsxSelfClosingElementContext, isNonJsxSameLineTokenContext], RuleAction.Space), - rule("NoSpaceBeforeGreaterThanTokenInJsxOpeningElement", SyntaxKind.SlashToken, SyntaxKind.GreaterThanToken, [isJsxSelfClosingElementContext, isNonJsxSameLineTokenContext], RuleAction.Delete), - rule("NoSpaceBeforeEqualInJsxAttribute", anyToken, SyntaxKind.EqualsToken, [isJsxAttributeContext, isNonJsxSameLineTokenContext], RuleAction.Delete), - rule("NoSpaceAfterEqualInJsxAttribute", SyntaxKind.EqualsToken, anyToken, [isJsxAttributeContext, isNonJsxSameLineTokenContext], RuleAction.Delete), + rule("SpaceBeforeJsxAttribute", anyToken, SyntaxKind.Identifier, [isNextTokenParentJsxAttribute, isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBeforeSlashInJsxOpeningElement", anyToken, SyntaxKind.SlashToken, [isJsxSelfClosingElementContext, isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceBeforeGreaterThanTokenInJsxOpeningElement", SyntaxKind.SlashToken, SyntaxKind.GreaterThanToken, [isJsxSelfClosingElementContext, isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeEqualInJsxAttribute", anyToken, SyntaxKind.EqualsToken, [isJsxAttributeContext, isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterEqualInJsxAttribute", SyntaxKind.EqualsToken, anyToken, [isJsxAttributeContext, isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), // TypeScript-specific rules // Use of module as a function call. e.g.: import m2 = module("m2"); - rule("NoSpaceAfterModuleImport", [SyntaxKind.ModuleKeyword, SyntaxKind.RequireKeyword], SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), + rule("NoSpaceAfterModuleImport", [SyntaxKind.ModuleKeyword, SyntaxKind.RequireKeyword], SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), // Add a space around certain TypeScript keywords rule( "SpaceAfterCertainTypeScriptKeywords", @@ -171,41 +174,41 @@ namespace ts.formatting { ], anyToken, [isNonJsxSameLineTokenContext], - RuleAction.Space), + RuleAction.InsertSpace), rule( "SpaceBeforeCertainTypeScriptKeywords", anyToken, [SyntaxKind.ExtendsKeyword, SyntaxKind.ImplementsKeyword, SyntaxKind.FromKeyword], [isNonJsxSameLineTokenContext], - RuleAction.Space), + RuleAction.InsertSpace), // Treat string literals in module names as identifiers, and add a space between the literal and the opening Brace braces, e.g.: module "m2" { - rule("SpaceAfterModuleName", SyntaxKind.StringLiteral, SyntaxKind.OpenBraceToken, [isModuleDeclContext], RuleAction.Space), + rule("SpaceAfterModuleName", SyntaxKind.StringLiteral, SyntaxKind.OpenBraceToken, [isModuleDeclContext], RuleAction.InsertSpace), // Lambda expressions - rule("SpaceBeforeArrow", anyToken, SyntaxKind.EqualsGreaterThanToken, [isNonJsxSameLineTokenContext], RuleAction.Space), - rule("SpaceAfterArrow", SyntaxKind.EqualsGreaterThanToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.Space), + rule("SpaceBeforeArrow", anyToken, SyntaxKind.EqualsGreaterThanToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceAfterArrow", SyntaxKind.EqualsGreaterThanToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), // Optional parameters and let args - rule("NoSpaceAfterEllipsis", SyntaxKind.DotDotDotToken, SyntaxKind.Identifier, [isNonJsxSameLineTokenContext], RuleAction.Delete), - rule("NoSpaceAfterOptionalParameters", SyntaxKind.QuestionToken, [SyntaxKind.CloseParenToken, SyntaxKind.CommaToken], [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.Delete), + rule("NoSpaceAfterEllipsis", SyntaxKind.DotDotDotToken, SyntaxKind.Identifier, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterOptionalParameters", SyntaxKind.QuestionToken, [SyntaxKind.CloseParenToken, SyntaxKind.CommaToken], [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.DeleteSpace), // Remove spaces in empty interface literals. e.g.: x: {} - rule("NoSpaceBetweenEmptyInterfaceBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectTypeContext], RuleAction.Delete), + rule("NoSpaceBetweenEmptyInterfaceBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectTypeContext], RuleAction.DeleteSpace), // generics and type assertions - rule("NoSpaceBeforeOpenAngularBracket", typeNames, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.Delete), - rule("NoSpaceBetweenCloseParenAndAngularBracket", SyntaxKind.CloseParenToken, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.Delete), - rule("NoSpaceAfterOpenAngularBracket", SyntaxKind.LessThanToken, anyToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.Delete), - rule("NoSpaceBeforeCloseAngularBracket", anyToken, SyntaxKind.GreaterThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.Delete), + rule("NoSpaceBeforeOpenAngularBracket", typeNames, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), + rule("NoSpaceBetweenCloseParenAndAngularBracket", SyntaxKind.CloseParenToken, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterOpenAngularBracket", SyntaxKind.LessThanToken, anyToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeCloseAngularBracket", anyToken, SyntaxKind.GreaterThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace), rule("NoSpaceAfterCloseAngularBracket", SyntaxKind.GreaterThanToken, [SyntaxKind.OpenParenToken, SyntaxKind.OpenBracketToken, SyntaxKind.GreaterThanToken, SyntaxKind.CommaToken], [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext, isNotFunctionDeclContext /*To prevent an interference with the SpaceBeforeOpenParenInFuncDecl rule*/], - RuleAction.Delete), + RuleAction.DeleteSpace), // decorators - rule("SpaceBeforeAt", [SyntaxKind.CloseParenToken, SyntaxKind.Identifier], SyntaxKind.AtToken, [isNonJsxSameLineTokenContext], RuleAction.Space), - rule("NoSpaceAfterAt", SyntaxKind.AtToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), + rule("SpaceBeforeAt", [SyntaxKind.CloseParenToken, SyntaxKind.Identifier], SyntaxKind.AtToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceAfterAt", SyntaxKind.AtToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), // Insert space after @ in decorator rule("SpaceAfterDecorator", anyToken, @@ -225,111 +228,113 @@ namespace ts.formatting { SyntaxKind.AsteriskToken, ], [isEndOfDecoratorContextOnSameLine], - RuleAction.Space), + RuleAction.InsertSpace), - rule("NoSpaceBeforeNonNullAssertionOperator", anyToken, SyntaxKind.ExclamationToken, [isNonJsxSameLineTokenContext, isNonNullAssertionContext], RuleAction.Delete), - rule("NoSpaceAfterNewKeywordOnConstructorSignature", SyntaxKind.NewKeyword, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isConstructorSignatureContext], RuleAction.Delete), - rule("SpaceLessThanAndNonJSXTypeAnnotation", SyntaxKind.LessThanToken, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext], RuleAction.Space), + rule("NoSpaceBeforeNonNullAssertionOperator", anyToken, SyntaxKind.ExclamationToken, [isNonJsxSameLineTokenContext, isNonNullAssertionContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterNewKeywordOnConstructorSignature", SyntaxKind.NewKeyword, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isConstructorSignatureContext], RuleAction.DeleteSpace), + rule("SpaceLessThanAndNonJSXTypeAnnotation", SyntaxKind.LessThanToken, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), ]; // These rules are applied after high priority const userConfigurableRules = [ // Treat constructor as an identifier in a function declaration, and remove spaces between constructor and following left parentheses - rule("SpaceAfterConstructor", SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterConstructor"), isNonJsxSameLineTokenContext], RuleAction.Space), - rule("NoSpaceAfterConstructor", SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterConstructor"), isNonJsxSameLineTokenContext], RuleAction.Delete), + rule("SpaceAfterConstructor", SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterConstructor"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceAfterConstructor", SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterConstructor"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("SpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionEnabled("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext, isNextTokenNotCloseBracket], RuleAction.Space), - rule("NoSpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext], RuleAction.Delete), + rule("SpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionEnabled("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext, isNextTokenNotCloseBracket], RuleAction.InsertSpace), + rule("NoSpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext], RuleAction.DeleteSpace), // Insert space after function keyword for anonymous functions - rule("SpaceAfterAnonymousFunctionKeyword", [SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken], SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), isFunctionDeclContext], RuleAction.Space), - rule("NoSpaceAfterAnonymousFunctionKeyword", [SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken], SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), isFunctionDeclContext], RuleAction.Delete), + rule("SpaceAfterAnonymousFunctionKeyword", [SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken], SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), isFunctionDeclContext], RuleAction.InsertSpace), + rule("NoSpaceAfterAnonymousFunctionKeyword", [SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken], SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), isFunctionDeclContext], RuleAction.DeleteSpace), // Insert space after keywords in control flow statements - rule("SpaceAfterKeywordInControl", keywords, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterKeywordsInControlFlowStatements"), isControlDeclContext], RuleAction.Space), - rule("NoSpaceAfterKeywordInControl", keywords, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterKeywordsInControlFlowStatements"), isControlDeclContext], RuleAction.Delete), + rule("SpaceAfterKeywordInControl", keywords, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterKeywordsInControlFlowStatements"), isControlDeclContext], RuleAction.InsertSpace), + rule("NoSpaceAfterKeywordInControl", keywords, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterKeywordsInControlFlowStatements"), isControlDeclContext], RuleAction.DeleteSpace), // Insert space after opening and before closing nonempty parenthesis - rule("SpaceAfterOpenParen", SyntaxKind.OpenParenToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.Space), - rule("SpaceBeforeCloseParen", anyToken, SyntaxKind.CloseParenToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.Space), - rule("SpaceBetweenOpenParens", SyntaxKind.OpenParenToken, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.Space), - rule("NoSpaceBetweenParens", SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), - rule("NoSpaceAfterOpenParen", SyntaxKind.OpenParenToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.Delete), - rule("NoSpaceBeforeCloseParen", anyToken, SyntaxKind.CloseParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.Delete), + rule("SpaceAfterOpenParen", SyntaxKind.OpenParenToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBeforeCloseParen", anyToken, SyntaxKind.CloseParenToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBetweenOpenParens", SyntaxKind.OpenParenToken, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceBetweenParens", SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterOpenParen", SyntaxKind.OpenParenToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeCloseParen", anyToken, SyntaxKind.CloseParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), // Insert space after opening and before closing nonempty brackets - rule("SpaceAfterOpenBracket", SyntaxKind.OpenBracketToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.Space), - rule("SpaceBeforeCloseBracket", anyToken, SyntaxKind.CloseBracketToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.Space), - rule("NoSpaceBetweenBrackets", SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), - rule("NoSpaceAfterOpenBracket", SyntaxKind.OpenBracketToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.Delete), - rule("NoSpaceBeforeCloseBracket", anyToken, SyntaxKind.CloseBracketToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.Delete), + rule("SpaceAfterOpenBracket", SyntaxKind.OpenBracketToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBeforeCloseBracket", anyToken, SyntaxKind.CloseBracketToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceBetweenBrackets", SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterOpenBracket", SyntaxKind.OpenBracketToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeCloseBracket", anyToken, SyntaxKind.CloseBracketToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), // Insert a space after { and before } in single-line contexts, but remove space from empty object literals {}. - rule("SpaceAfterOpenBrace", SyntaxKind.OpenBraceToken, anyToken, [isOptionEnabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isBraceWrappedContext], RuleAction.Space), - rule("SpaceBeforeCloseBrace", anyToken, SyntaxKind.CloseBraceToken, [isOptionEnabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isBraceWrappedContext], RuleAction.Space), - rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectContext], RuleAction.Delete), - rule("NoSpaceAfterOpenBrace", SyntaxKind.OpenBraceToken, anyToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isNonJsxSameLineTokenContext], RuleAction.Delete), - rule("NoSpaceBeforeCloseBrace", anyToken, SyntaxKind.CloseBraceToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isNonJsxSameLineTokenContext], RuleAction.Delete), + rule("SpaceAfterOpenBrace", SyntaxKind.OpenBraceToken, anyToken, [isOptionEnabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isBraceWrappedContext], RuleAction.InsertSpace), + rule("SpaceBeforeCloseBrace", anyToken, SyntaxKind.CloseBraceToken, [isOptionEnabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isBraceWrappedContext], RuleAction.InsertSpace), + rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterOpenBrace", SyntaxKind.OpenBraceToken, anyToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeCloseBrace", anyToken, SyntaxKind.CloseBraceToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), // Insert space after opening and before closing template string braces - rule("SpaceAfterTemplateHeadAndMiddle", [SyntaxKind.TemplateHead, SyntaxKind.TemplateMiddle], anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxSameLineTokenContext], RuleAction.Space), - rule("SpaceBeforeTemplateMiddleAndTail", anyToken, [SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail], [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxSameLineTokenContext], RuleAction.Space), - rule("NoSpaceAfterTemplateHeadAndMiddle", [SyntaxKind.TemplateHead, SyntaxKind.TemplateMiddle], anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxSameLineTokenContext], RuleAction.Delete), - rule("NoSpaceBeforeTemplateMiddleAndTail", anyToken, [SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail], [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxSameLineTokenContext], RuleAction.Delete), + rule("SpaceAfterTemplateHeadAndMiddle", [SyntaxKind.TemplateHead, SyntaxKind.TemplateMiddle], anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("SpaceBeforeTemplateMiddleAndTail", anyToken, [SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail], [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace), + rule("NoSpaceAfterTemplateHeadAndMiddle", [SyntaxKind.TemplateHead, SyntaxKind.TemplateMiddle], anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeTemplateMiddleAndTail", anyToken, [SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail], [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), // No space after { and before } in JSX expression - rule("SpaceAfterOpenBraceInJsxExpression", SyntaxKind.OpenBraceToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.Space), - rule("SpaceBeforeCloseBraceInJsxExpression", anyToken, SyntaxKind.CloseBraceToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.Space), - rule("NoSpaceAfterOpenBraceInJsxExpression", SyntaxKind.OpenBraceToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.Delete), - rule("NoSpaceBeforeCloseBraceInJsxExpression", anyToken, SyntaxKind.CloseBraceToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.Delete), + rule("SpaceAfterOpenBraceInJsxExpression", SyntaxKind.OpenBraceToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.InsertSpace), + rule("SpaceBeforeCloseBraceInJsxExpression", anyToken, SyntaxKind.CloseBraceToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.InsertSpace), + rule("NoSpaceAfterOpenBraceInJsxExpression", SyntaxKind.OpenBraceToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.DeleteSpace), + rule("NoSpaceBeforeCloseBraceInJsxExpression", anyToken, SyntaxKind.CloseBraceToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.DeleteSpace), // Insert space after semicolon in for statement - rule("SpaceAfterSemicolonInFor", SyntaxKind.SemicolonToken, anyToken, [isOptionEnabled("insertSpaceAfterSemicolonInForStatements"), isNonJsxSameLineTokenContext, isForContext], RuleAction.Space), - rule("NoSpaceAfterSemicolonInFor", SyntaxKind.SemicolonToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterSemicolonInForStatements"), isNonJsxSameLineTokenContext, isForContext], RuleAction.Delete), + rule("SpaceAfterSemicolonInFor", SyntaxKind.SemicolonToken, anyToken, [isOptionEnabled("insertSpaceAfterSemicolonInForStatements"), isNonJsxSameLineTokenContext, isForContext], RuleAction.InsertSpace), + rule("NoSpaceAfterSemicolonInFor", SyntaxKind.SemicolonToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterSemicolonInForStatements"), isNonJsxSameLineTokenContext, isForContext], RuleAction.DeleteSpace), // Insert space before and after binary operators - rule("SpaceBeforeBinaryOperator", anyToken, binaryOperators, [isOptionEnabled("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Space), - rule("SpaceAfterBinaryOperator", binaryOperators, anyToken, [isOptionEnabled("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Space), - rule("NoSpaceBeforeBinaryOperator", anyToken, binaryOperators, [isOptionDisabledOrUndefined("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Delete), - rule("NoSpaceAfterBinaryOperator", binaryOperators, anyToken, [isOptionDisabledOrUndefined("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Delete), + rule("SpaceBeforeBinaryOperator", anyToken, binaryOperators, [isOptionEnabled("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("SpaceAfterBinaryOperator", binaryOperators, anyToken, [isOptionEnabled("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace), + rule("NoSpaceBeforeBinaryOperator", anyToken, binaryOperators, [isOptionDisabledOrUndefined("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterBinaryOperator", binaryOperators, anyToken, [isOptionDisabledOrUndefined("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.DeleteSpace), - rule("SpaceBeforeOpenParenInFuncDecl", anyToken, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceBeforeFunctionParenthesis"), isNonJsxSameLineTokenContext, isFunctionDeclContext], RuleAction.Space), - rule("NoSpaceBeforeOpenParenInFuncDecl", anyToken, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceBeforeFunctionParenthesis"), isNonJsxSameLineTokenContext, isFunctionDeclContext], RuleAction.Delete), + rule("SpaceBeforeOpenParenInFuncDecl", anyToken, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceBeforeFunctionParenthesis"), isNonJsxSameLineTokenContext, isFunctionDeclContext], RuleAction.InsertSpace), + rule("NoSpaceBeforeOpenParenInFuncDecl", anyToken, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceBeforeFunctionParenthesis"), isNonJsxSameLineTokenContext, isFunctionDeclContext], RuleAction.DeleteSpace), // Open Brace braces after control block - rule("NewLineBeforeOpenBraceInControl", controlOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForControlBlocks"), isControlDeclContext, isBeforeMultilineBlockContext], RuleAction.NewLine, RuleFlags.CanDeleteNewLines), + rule("NewLineBeforeOpenBraceInControl", controlOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForControlBlocks"), isControlDeclContext, isBeforeMultilineBlockContext], RuleAction.InsertNewLine, RuleFlags.CanDeleteNewLines), // Open Brace braces after function // TypeScript: Function can have return types, which can be made of tons of different token kinds - rule("NewLineBeforeOpenBraceInFunction", functionOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForFunctions"), isFunctionDeclContext, isBeforeMultilineBlockContext], RuleAction.NewLine, RuleFlags.CanDeleteNewLines), + rule("NewLineBeforeOpenBraceInFunction", functionOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForFunctions"), isFunctionDeclContext, isBeforeMultilineBlockContext], RuleAction.InsertNewLine, RuleFlags.CanDeleteNewLines), // Open Brace braces after TypeScript module/class/interface - rule("NewLineBeforeOpenBraceInTypeScriptDeclWithBlock", typeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForFunctions"), isTypeScriptDeclWithBlockContext, isBeforeMultilineBlockContext], RuleAction.NewLine, RuleFlags.CanDeleteNewLines), + rule("NewLineBeforeOpenBraceInTypeScriptDeclWithBlock", typeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForFunctions"), isTypeScriptDeclWithBlockContext, isBeforeMultilineBlockContext], RuleAction.InsertNewLine, RuleFlags.CanDeleteNewLines), - rule("SpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionEnabled("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.Space), - rule("NoSpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.Delete), + rule("SpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionEnabled("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.InsertSpace), + rule("NoSpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.DeleteSpace), - rule("SpaceBeforeTypeAnnotation", anyToken, SyntaxKind.ColonToken, [isOptionEnabled("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.Space), - rule("NoSpaceBeforeTypeAnnotation", anyToken, SyntaxKind.ColonToken, [isOptionDisabledOrUndefined("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.Delete), + rule("SpaceBeforeTypeAnnotation", anyToken, SyntaxKind.ColonToken, [isOptionEnabled("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.InsertSpace), + rule("NoSpaceBeforeTypeAnnotation", anyToken, SyntaxKind.ColonToken, [isOptionDisabledOrUndefined("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.DeleteSpace), + rule("NoOptionalSemicolon", SyntaxKind.SemicolonToken, anyTokenIncludingEOF, [optionEquals("semicolons", SemicolonPreference.Remove), isSemicolonDeletionContext], RuleAction.DeleteToken), + rule("OptionalSemicolon", anyToken, anyTokenIncludingEOF, [optionEquals("semicolons", SemicolonPreference.Insert), isSemicolonInsertionContext], RuleAction.InsertTrailingSemicolon), ]; // These rules are lower in priority than user-configurable. Rules earlier in this list have priority over rules later in the list. const lowPriorityCommonRules = [ // Space after keyword but not before ; or : or ? - rule("NoSpaceBeforeSemicolon", anyToken, SyntaxKind.SemicolonToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), + rule("NoSpaceBeforeSemicolon", anyToken, SyntaxKind.SemicolonToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), - rule("SpaceBeforeOpenBraceInControl", controlOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForControlBlocks"), isControlDeclContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.Space, RuleFlags.CanDeleteNewLines), - rule("SpaceBeforeOpenBraceInFunction", functionOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForFunctions"), isFunctionDeclContext, isBeforeBlockContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.Space, RuleFlags.CanDeleteNewLines), - rule("SpaceBeforeOpenBraceInTypeScriptDeclWithBlock", typeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForFunctions"), isTypeScriptDeclWithBlockContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.Space, RuleFlags.CanDeleteNewLines), + rule("SpaceBeforeOpenBraceInControl", controlOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForControlBlocks"), isControlDeclContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), + rule("SpaceBeforeOpenBraceInFunction", functionOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForFunctions"), isFunctionDeclContext, isBeforeBlockContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), + rule("SpaceBeforeOpenBraceInTypeScriptDeclWithBlock", typeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForFunctions"), isTypeScriptDeclWithBlockContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines), - rule("NoSpaceBeforeComma", anyToken, SyntaxKind.CommaToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), + rule("NoSpaceBeforeComma", anyToken, SyntaxKind.CommaToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), // No space before and after indexer `x[]` - rule("NoSpaceBeforeOpenBracket", anyTokenExcept(SyntaxKind.AsyncKeyword, SyntaxKind.CaseKeyword), SyntaxKind.OpenBracketToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), - rule("NoSpaceAfterCloseBracket", SyntaxKind.CloseBracketToken, anyToken, [isNonJsxSameLineTokenContext, isNotBeforeBlockInFunctionDeclarationContext], RuleAction.Delete), - rule("SpaceAfterSemicolon", SyntaxKind.SemicolonToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.Space), + rule("NoSpaceBeforeOpenBracket", anyTokenExcept(SyntaxKind.AsyncKeyword, SyntaxKind.CaseKeyword), SyntaxKind.OpenBracketToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), + rule("NoSpaceAfterCloseBracket", SyntaxKind.CloseBracketToken, anyToken, [isNonJsxSameLineTokenContext, isNotBeforeBlockInFunctionDeclarationContext], RuleAction.DeleteSpace), + rule("SpaceAfterSemicolon", SyntaxKind.SemicolonToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), // Remove extra space between for and await - rule("SpaceBetweenForAndAwaitKeyword", SyntaxKind.ForKeyword, SyntaxKind.AwaitKeyword, [isNonJsxSameLineTokenContext], RuleAction.Space), + rule("SpaceBetweenForAndAwaitKeyword", SyntaxKind.ForKeyword, SyntaxKind.AwaitKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), // Add a space between statements. All keywords except (do,else,case) has open/close parens after them. // So, we have a rule to add a space for [),Any], [do,Any], [else,Any], and [case,Any] @@ -338,9 +343,9 @@ namespace ts.formatting { [SyntaxKind.CloseParenToken, SyntaxKind.DoKeyword, SyntaxKind.ElseKeyword, SyntaxKind.CaseKeyword], anyToken, [isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext, isNotForContext], - RuleAction.Space), + RuleAction.InsertSpace), // This low-pri rule takes care of "try {" and "finally {" in case the rule SpaceBeforeOpenBraceInControl didn't execute on FormatOnEnter. - rule("SpaceAfterTryFinally", [SyntaxKind.TryKeyword, SyntaxKind.FinallyKeyword], SyntaxKind.OpenBraceToken, [isNonJsxSameLineTokenContext], RuleAction.Space), + rule("SpaceAfterTryFinally", [SyntaxKind.TryKeyword, SyntaxKind.FinallyKeyword], SyntaxKind.OpenBraceToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), ]; return [ @@ -395,6 +400,10 @@ namespace ts.formatting { /// Contexts /// + function optionEquals(optionName: K, optionValue: FormatCodeSettings[K]): (context: FormattingContext) => boolean { + return (context) => context.options && context.options[optionName] === optionValue; + } + function isOptionEnabled(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { return (context) => context.options && context.options.hasOwnProperty(optionName) && !!context.options[optionName]; } @@ -780,4 +789,79 @@ namespace ts.formatting { function isNonNullAssertionContext(context: FormattingContext): boolean { return context.contextNode.kind === SyntaxKind.NonNullExpression; } + + function isSemicolonDeletionContext(context: FormattingContext): boolean { + let nextTokenKind = context.nextTokenSpan.kind; + let nextTokenStart = context.nextTokenSpan.pos; + if (isTrivia(nextTokenKind)) { + const nextRealToken = context.nextTokenParent === context.currentTokenParent + ? findNextToken( + context.currentTokenParent, + findAncestor(context.currentTokenParent, a => !a.parent)!, + context.sourceFile) + : context.nextTokenParent.getFirstToken(context.sourceFile); + if (!nextRealToken) { + return true; + } + nextTokenKind = nextRealToken.kind; + nextTokenStart = nextRealToken.getStart(context.sourceFile); + } + + const startLine = context.sourceFile.getLineAndCharacterOfPosition(context.currentTokenSpan.pos).line; + const endLine = context.sourceFile.getLineAndCharacterOfPosition(nextTokenStart).line; + if (startLine === endLine) { + return nextTokenKind === SyntaxKind.CloseBraceToken + || nextTokenKind === SyntaxKind.EndOfFileToken; + } + + if (nextTokenKind === SyntaxKind.SemicolonClassElement || + nextTokenKind === SyntaxKind.SemicolonToken + ) { + return false; + } + + if (context.contextNode.kind === SyntaxKind.InterfaceDeclaration || + context.contextNode.kind === SyntaxKind.TypeAliasDeclaration + ) { + // Can’t remove semicolon after `foo`; it would parse as a method declaration: + // + // interface I { + // foo; + // (): void + // } + return !isPropertySignature(context.currentTokenParent) + || !!context.currentTokenParent.type + || nextTokenKind !== SyntaxKind.OpenParenToken; + } + + if (isPropertyDeclaration(context.currentTokenParent)) { + return !context.currentTokenParent.initializer; + } + + return context.currentTokenParent.kind !== SyntaxKind.ForStatement + && context.currentTokenParent.kind !== SyntaxKind.EmptyStatement + && context.currentTokenParent.kind !== SyntaxKind.SemicolonClassElement + && nextTokenKind !== SyntaxKind.OpenBracketToken + && nextTokenKind !== SyntaxKind.OpenParenToken + && nextTokenKind !== SyntaxKind.PlusToken + && nextTokenKind !== SyntaxKind.MinusToken + && nextTokenKind !== SyntaxKind.SlashToken + && nextTokenKind !== SyntaxKind.RegularExpressionLiteral + && nextTokenKind !== SyntaxKind.CommaToken + && nextTokenKind !== SyntaxKind.TemplateExpression + && nextTokenKind !== SyntaxKind.TemplateHead + && nextTokenKind !== SyntaxKind.NoSubstitutionTemplateLiteral + && nextTokenKind !== SyntaxKind.DotToken; + } + + function isSemicolonInsertionContext(context: FormattingContext): boolean { + const contextAncestor = findAncestor(context.currentTokenParent, ancestor => { + if (ancestor.end !== context.currentTokenSpan.end) { + return "quit"; + } + return syntaxMayBeASICandidate(ancestor.kind); + }); + + return !!contextAncestor && isASICandidate(contextAncestor, context.sourceFile); + } } diff --git a/src/services/formatting/rulesMap.ts b/src/services/formatting/rulesMap.ts index 9c57fa8f8c8ab..f466b397d20dd 100644 --- a/src/services/formatting/rulesMap.ts +++ b/src/services/formatting/rulesMap.ts @@ -1,7 +1,7 @@ /* @internal */ namespace ts.formatting { export function getFormatContext(options: FormatCodeSettings): FormatContext { - return { options, getRule: getRulesMap() }; + return { options, getRules: getRulesMap() }; } let rulesMapCache: RulesMap | undefined; @@ -13,12 +13,46 @@ namespace ts.formatting { return rulesMapCache; } - export type RulesMap = (context: FormattingContext) => Rule | undefined; + /** + * For a given rule action, gets a mask of other rule actions that + * cannot be applied at the same position. + */ + function getRuleActionExclusion(ruleAction: RuleAction): RuleAction { + let mask: RuleAction = 0; + if (ruleAction & RuleAction.StopProcessingSpaceActions) { + mask |= RuleAction.ModifySpaceAction; + } + if (ruleAction & RuleAction.StopProcessingTokenActions) { + mask |= RuleAction.ModifyTokenAction; + } + if (ruleAction & RuleAction.ModifySpaceAction) { + mask |= RuleAction.ModifySpaceAction; + } + if (ruleAction & RuleAction.ModifyTokenAction) { + mask |= RuleAction.ModifyTokenAction; + } + return mask; + } + + export type RulesMap = (context: FormattingContext) => readonly Rule[] | undefined; function createRulesMap(rules: readonly RuleSpec[]): RulesMap { const map = buildMap(rules); return context => { const bucket = map[getRuleBucketIndex(context.currentTokenSpan.kind, context.nextTokenSpan.kind)]; - return bucket && find(bucket, rule => every(rule.context, c => c(context))); + if (bucket) { + const rules: Rule[] = []; + let ruleActionMask: RuleAction = 0; + for (const rule of bucket) { + const acceptRuleActions = ~getRuleActionExclusion(ruleActionMask); + if (rule.action & acceptRuleActions && every(rule.context, c => c(context))) { + rules.push(rule); + ruleActionMask |= rule.action; + } + } + if (rules.length) { + return rules; + } + } }; } @@ -54,8 +88,8 @@ namespace ts.formatting { const mapRowLength = SyntaxKind.LastToken + 1; enum RulesPosition { - IgnoreRulesSpecific = 0, - IgnoreRulesAny = maskBitSize * 1, + StopRulesSpecific = 0, + StopRulesAny = maskBitSize * 1, ContextRulesSpecific = maskBitSize * 2, ContextRulesAny = maskBitSize * 3, NoContextRulesSpecific = maskBitSize * 4, @@ -78,8 +112,8 @@ namespace ts.formatting { // In order to insert a rule to the end of sub-bucket (3), we get the index by adding // the values in the bitmap segments 3rd, 2nd, and 1st. function addRule(rules: Rule[], rule: Rule, specificTokens: boolean, constructionState: number[], rulesBucketIndex: number): void { - const position = rule.action === RuleAction.Ignore ? - specificTokens ? RulesPosition.IgnoreRulesSpecific : RulesPosition.IgnoreRulesAny : + const position = rule.action & RuleAction.StopAction ? + specificTokens ? RulesPosition.StopRulesSpecific : RulesPosition.StopRulesAny : rule.context !== anyContext ? specificTokens ? RulesPosition.ContextRulesSpecific : RulesPosition.ContextRulesAny : specificTokens ? RulesPosition.NoContextRulesSpecific : RulesPosition.NoContextRulesAny; diff --git a/src/services/getEditsForFileRename.ts b/src/services/getEditsForFileRename.ts index e2428eeaade1d..9ab8410f51aeb 100644 --- a/src/services/getEditsForFileRename.ts +++ b/src/services/getEditsForFileRename.ts @@ -6,14 +6,14 @@ namespace ts { newFileOrDirPath: string, host: LanguageServiceHost, formatContext: formatting.FormatContext, - _preferences: UserPreferences, + preferences: UserPreferences, sourceMapper: SourceMapper, ): readonly FileTextChanges[] { const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host); const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); const oldToNew = getPathUpdater(oldFileOrDirPath, newFileOrDirPath, getCanonicalFileName, sourceMapper); const newToOld = getPathUpdater(newFileOrDirPath, oldFileOrDirPath, getCanonicalFileName, sourceMapper); - return textChanges.ChangeTracker.with({ host, formatContext }, changeTracker => { + return textChanges.ChangeTracker.with({ host, formatContext, preferences }, changeTracker => { updateTsconfigFiles(program, changeTracker, oldToNew, oldFileOrDirPath, newFileOrDirPath, host.getCurrentDirectory(), useCaseSensitiveFileNames); updateImports(program, changeTracker, oldToNew, newToOld, host, getCanonicalFileName); }); diff --git a/src/services/organizeImports.ts b/src/services/organizeImports.ts index bf4545dbbd04b..6db5c9eeadc8c 100644 --- a/src/services/organizeImports.ts +++ b/src/services/organizeImports.ts @@ -12,10 +12,10 @@ namespace ts.OrganizeImports { formatContext: formatting.FormatContext, host: LanguageServiceHost, program: Program, - _preferences: UserPreferences, + preferences: UserPreferences, ) { - const changeTracker = textChanges.ChangeTracker.fromContext({ host, formatContext }); + const changeTracker = textChanges.ChangeTracker.fromContext({ host, formatContext, preferences }); const coalesceAndOrganizeImports = (importGroup: readonly ImportDeclaration[]) => coalesceImports(removeUnusedImports(importGroup, sourceFile, program)); diff --git a/src/services/services.ts b/src/services/services.ts index 644629ce2af4f..c52bab082f4db 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -99,7 +99,7 @@ namespace ts { return this._children || (this._children = createChildren(this, sourceFile)); } - public getFirstToken(sourceFile?: SourceFile): Node | undefined { + public getFirstToken(sourceFile?: SourceFileLike): Node | undefined { this.assertHasRealPosition(); const children = this.getChildren(sourceFile); if (!children.length) { @@ -112,7 +112,7 @@ namespace ts { child.getFirstToken(sourceFile); } - public getLastToken(sourceFile?: SourceFile): Node | undefined { + public getLastToken(sourceFile?: SourceFileLike): Node | undefined { this.assertHasRealPosition(); const children = this.getChildren(sourceFile); diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index e57b17247ccb1..03e7a1c458d33 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -218,6 +218,7 @@ namespace ts.textChanges { export interface TextChangesContext { host: LanguageServiceHost; formatContext: formatting.FormatContext; + preferences: UserPreferences; } export type TypeAnnotatable = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature; @@ -831,28 +832,36 @@ namespace ts.textChanges { return (options.prefix || "") + noIndent + (options.suffix || ""); } + function getFormatCodeSettingsForWriting({ options }: formatting.FormatContext, sourceFile: SourceFile): FormatCodeSettings { + const shouldAutoDetectSemicolonPreference = !options.semicolons || options.semicolons === SemicolonPreference.Ignore; + const shouldRemoveSemicolons = options.semicolons === SemicolonPreference.Remove || shouldAutoDetectSemicolonPreference && !probablyUsesSemicolons(sourceFile); + return { + ...options, + semicolons: shouldRemoveSemicolons ? SemicolonPreference.Remove : SemicolonPreference.Ignore, + }; + } + /** Note: this may mutate `nodeIn`. */ function getFormattedTextOfNode(nodeIn: Node, sourceFile: SourceFile, pos: number, { indentation, prefix, delta }: InsertNodeOptions, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): string { const { node, text } = getNonformattedText(nodeIn, sourceFile, newLineCharacter); if (validate) validate(node, text); - const { options: formatOptions } = formatContext; + const formatOptions = getFormatCodeSettingsForWriting(formatContext, sourceFile); const initialIndentation = indentation !== undefined ? indentation : formatting.SmartIndenter.getIndentation(pos, sourceFile, formatOptions, prefix === newLineCharacter || getLineStartPositionForPosition(pos, sourceFile) === pos); if (delta === undefined) { - delta = formatting.SmartIndenter.shouldIndentChildNode(formatContext.options, nodeIn) ? (formatOptions.indentSize || 0) : 0; + delta = formatting.SmartIndenter.shouldIndentChildNode(formatOptions, nodeIn) ? (formatOptions.indentSize || 0) : 0; } const file: SourceFileLike = { text, getLineAndCharacterOfPosition(pos) { return getLineAndCharacterOfPosition(this, pos); } }; - const changes = formatting.formatNodeGivenIndentation(node, file, sourceFile.languageVariant, initialIndentation, delta, formatContext); + const changes = formatting.formatNodeGivenIndentation(node, file, sourceFile.languageVariant, initialIndentation, delta, { ...formatContext, options: formatOptions }); return applyChanges(text, changes); } /** Note: output node may be mutated input node. */ export function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } { - const omitTrailingSemicolon = !!sourceFile && !probablyUsesSemicolons(sourceFile); - const writer = createWriter(newLineCharacter, omitTrailingSemicolon); + const writer = createWriter(newLineCharacter); const newLine = newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed; createPrinter({ newLine, neverAsciiEscape: true }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer); return { text: writer.getText(), node: assignPositionsToNode(node) }; @@ -894,11 +903,11 @@ namespace ts.textChanges { interface TextChangesWriter extends EmitTextWriter, PrintHandlers {} - function createWriter(newLine: string, omitTrailingSemicolon?: boolean): TextChangesWriter { + function createWriter(newLine: string): TextChangesWriter { let lastNonTriviaPosition = 0; - const writer = omitTrailingSemicolon ? getTrailingSemicolonOmittingWriter(createTextWriter(newLine)) : createTextWriter(newLine); + const writer = createTextWriter(newLine); const onEmitNode: PrintHandlers["onEmitNode"] = (hint, node, printCallback) => { if (node) { setPos(node, lastNonTriviaPosition); diff --git a/src/services/types.ts b/src/services/types.ts index c44370febe399..75e8b2680ef75 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -17,7 +17,11 @@ namespace ts { getFullText(sourceFile?: SourceFile): string; getText(sourceFile?: SourceFile): string; getFirstToken(sourceFile?: SourceFile): Node | undefined; + /* @internal */ + getFirstToken(sourceFile?: SourceFileLike): Node | undefined; // eslint-disable-line @typescript-eslint/unified-signatures getLastToken(sourceFile?: SourceFile): Node | undefined; + /* @internal */ + getLastToken(sourceFile?: SourceFileLike): Node | undefined; // eslint-disable-line @typescript-eslint/unified-signatures // See ts.forEachChild for documentation. forEachChild(cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray) => T | undefined): T | undefined; } @@ -676,6 +680,12 @@ namespace ts { Smart = 2, } + export enum SemicolonPreference { + Ignore = "ignore", + Insert = "insert", + Remove = "remove", + } + /* @deprecated - consider using EditorSettings instead */ export interface EditorOptions { BaseIndentSize?: number; @@ -734,6 +744,7 @@ namespace ts { readonly placeOpenBraceOnNewLineForControlBlocks?: boolean; readonly insertSpaceBeforeTypeAnnotation?: boolean; readonly indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean; + readonly semicolons?: SemicolonPreference; } export function getDefaultFormatCodeSettings(newLineCharacter?: string): FormatCodeSettings { @@ -757,6 +768,7 @@ namespace ts { insertSpaceBeforeFunctionParenthesis: false, placeOpenBraceOnNewLineForFunctions: false, placeOpenBraceOnNewLineForControlBlocks: false, + semicolons: SemicolonPreference.Ignore, }; } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 91b0a7a19914b..5f59cef602bb3 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -749,7 +749,7 @@ namespace ts { return findPrecedingToken(position, file); } - export function findNextToken(previousToken: Node, parent: Node, sourceFile: SourceFile): Node | undefined { + export function findNextToken(previousToken: Node, parent: Node, sourceFile: SourceFileLike): Node | undefined { return find(parent); function find(n: Node): Node | undefined { @@ -757,7 +757,7 @@ namespace ts { // this is token that starts at the end of previous token - return it return n; } - return firstDefined(n.getChildren(), child => { + return firstDefined(n.getChildren(sourceFile), child => { const shouldDiveInChildNode = // previous token is enclosed somewhere in the child (child.pos <= previousToken.pos && child.end > previousToken.end) || @@ -1989,7 +1989,27 @@ namespace ts { return typeIsAccessible ? res : undefined; } - export function syntaxUsuallyHasTrailingSemicolon(kind: SyntaxKind) { + export function syntaxRequiresTrailingCommaOrSemicolonOrASI(kind: SyntaxKind) { + return kind === SyntaxKind.CallSignature + || kind === SyntaxKind.ConstructSignature + || kind === SyntaxKind.IndexSignature + || kind === SyntaxKind.PropertySignature + || kind === SyntaxKind.MethodSignature; + } + + export function syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(kind: SyntaxKind) { + return kind === SyntaxKind.FunctionDeclaration + || kind === SyntaxKind.Constructor + || kind === SyntaxKind.MethodDeclaration + || kind === SyntaxKind.GetAccessor + || kind === SyntaxKind.SetAccessor; + } + + export function syntaxRequiresTrailingModuleBlockOrSemicolonOrASI(kind: SyntaxKind) { + return kind === SyntaxKind.ModuleDeclaration; + } + + export function syntaxRequiresTrailingSemicolonOrASI(kind: SyntaxKind) { return kind === SyntaxKind.VariableStatement || kind === SyntaxKind.ExpressionStatement || kind === SyntaxKind.DoStatement @@ -2002,7 +2022,58 @@ namespace ts { || kind === SyntaxKind.TypeAliasDeclaration || kind === SyntaxKind.ImportDeclaration || kind === SyntaxKind.ImportEqualsDeclaration - || kind === SyntaxKind.ExportDeclaration; + || kind === SyntaxKind.ExportDeclaration + || kind === SyntaxKind.NamespaceExportDeclaration + || kind === SyntaxKind.ExportAssignment; + } + + export const syntaxMayBeASICandidate = or( + syntaxRequiresTrailingCommaOrSemicolonOrASI, + syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI, + syntaxRequiresTrailingModuleBlockOrSemicolonOrASI, + syntaxRequiresTrailingSemicolonOrASI); + + export function isASICandidate(node: Node, sourceFile: SourceFileLike): boolean { + const lastToken = node.getLastToken(sourceFile); + if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) { + return false; + } + + if (syntaxRequiresTrailingCommaOrSemicolonOrASI(node.kind)) { + if (lastToken && lastToken.kind === SyntaxKind.CommaToken) { + return false; + } + } + else if (syntaxRequiresTrailingModuleBlockOrSemicolonOrASI(node.kind)) { + const lastChild = last(node.getChildren(sourceFile)); + if (lastChild && isModuleBlock(lastChild)) { + return false; + } + } + else if (syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(node.kind)) { + const lastChild = last(node.getChildren(sourceFile)); + if (lastChild && isFunctionBlock(lastChild)) { + return false; + } + } + else if (!syntaxRequiresTrailingSemicolonOrASI(node.kind)) { + return false; + } + + // See comment in parser’s `parseDoStatement` + if (node.kind === SyntaxKind.DoStatement) { + return true; + } + + const topNode = findAncestor(node, ancestor => !ancestor.parent)!; + const nextToken = findNextToken(node, topNode, sourceFile); + if (!nextToken || nextToken.kind === SyntaxKind.CloseBraceToken) { + return true; + } + + const startLine = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line; + const endLine = sourceFile.getLineAndCharacterOfPosition(nextToken.getStart(sourceFile)).line; + return startLine !== endLine; } export function probablyUsesSemicolons(sourceFile: SourceFile): boolean { @@ -2010,7 +2081,7 @@ namespace ts { let withoutSemicolon = 0; const nStatementsToObserve = 5; forEachChild(sourceFile, function visit(node): boolean | undefined { - if (syntaxUsuallyHasTrailingSemicolon(node.kind)) { + if (syntaxRequiresTrailingSemicolonOrASI(node.kind)) { const lastToken = node.getLastToken(sourceFile); if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) { withSemicolon++; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index fb98e740ba388..ca07eebc536f4 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5294,6 +5294,11 @@ declare namespace ts { Block = 1, Smart = 2 } + enum SemicolonPreference { + Ignore = "ignore", + Insert = "insert", + Remove = "remove" + } interface EditorOptions { BaseIndentSize?: number; IndentSize: number; @@ -5346,6 +5351,7 @@ declare namespace ts { readonly placeOpenBraceOnNewLineForControlBlocks?: boolean; readonly insertSpaceBeforeTypeAnnotation?: boolean; readonly indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean; + readonly semicolons?: SemicolonPreference; } function getDefaultFormatCodeSettings(newLineCharacter?: string): FormatCodeSettings; interface DefinitionInfo extends DocumentSpan { @@ -8193,6 +8199,11 @@ declare namespace ts.server.protocol { Block = "Block", Smart = "Smart" } + enum SemicolonPreference { + Ignore = "ignore", + Insert = "insert", + Remove = "remove" + } interface EditorSettings { baseIndentSize?: number; indentSize?: number; @@ -8218,6 +8229,7 @@ declare namespace ts.server.protocol { placeOpenBraceOnNewLineForFunctions?: boolean; placeOpenBraceOnNewLineForControlBlocks?: boolean; insertSpaceBeforeTypeAnnotation?: boolean; + semicolons?: SemicolonPreference; } interface UserPreferences { readonly disableSuggestions?: boolean; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index b0d81933fdb07..d24f1e9c89cc8 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5294,6 +5294,11 @@ declare namespace ts { Block = 1, Smart = 2 } + enum SemicolonPreference { + Ignore = "ignore", + Insert = "insert", + Remove = "remove" + } interface EditorOptions { BaseIndentSize?: number; IndentSize: number; @@ -5346,6 +5351,7 @@ declare namespace ts { readonly placeOpenBraceOnNewLineForControlBlocks?: boolean; readonly insertSpaceBeforeTypeAnnotation?: boolean; readonly indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean; + readonly semicolons?: SemicolonPreference; } function getDefaultFormatCodeSettings(newLineCharacter?: string): FormatCodeSettings; interface DefinitionInfo extends DocumentSpan { diff --git a/tests/cases/fourslash/codeFixInferFromCallInAssignment.ts b/tests/cases/fourslash/codeFixInferFromCallInAssignment.ts index 85e1d3bc50952..5eb564b830573 100644 --- a/tests/cases/fourslash/codeFixInferFromCallInAssignment.ts +++ b/tests/cases/fourslash/codeFixInferFromCallInAssignment.ts @@ -6,4 +6,4 @@ //// return result //// } -verify.rangeAfterCodeFix("app: { use: (arg0: string) => any; }"); +verify.rangeAfterCodeFix("app: { use: (arg0: string) => any }"); diff --git a/tests/cases/fourslash/codeFixInferFromFunctionUsage.ts b/tests/cases/fourslash/codeFixInferFromFunctionUsage.ts index f2dbad67b4b7d..31068469b4db9 100644 --- a/tests/cases/fourslash/codeFixInferFromFunctionUsage.ts +++ b/tests/cases/fourslash/codeFixInferFromFunctionUsage.ts @@ -2,7 +2,7 @@ // @noImplicitAny: true ////function wrap( [| arr |] ) { -//// arr.other(function (a: number, b: number) { return a < b ? -1 : 1 }) +//// arr.other(function (a: number, b: number) { return a < b ? -1 : 1 }); //// } // https://github.com/Microsoft/TypeScript/issues/29330 diff --git a/tests/cases/fourslash/extract-method28.ts b/tests/cases/fourslash/extract-method28.ts new file mode 100644 index 0000000000000..a1e1189102c3f --- /dev/null +++ b/tests/cases/fourslash/extract-method28.ts @@ -0,0 +1,20 @@ +/// + +/////*a*/console.log(0) // +////console.log(0)/*b*/ + +format.setFormatOptions({ ...format.copyFormatOptions(), semicolons: ts.SemicolonPreference.Insert }); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "function_scope_0", + actionDescription: "Extract to function in global scope", + newContent: +`/*RENAME*/newFunction(); + +function newFunction() { + console.log(0); // + console.log(0); +} +` +}); diff --git a/tests/cases/fourslash/extract-method29.ts b/tests/cases/fourslash/extract-method29.ts new file mode 100644 index 0000000000000..a7f83e65340be --- /dev/null +++ b/tests/cases/fourslash/extract-method29.ts @@ -0,0 +1,20 @@ +/// + +/////*a*/console.log(0); // +////console.log(0);/*b*/ + +format.setFormatOptions({ ...format.copyFormatOptions(), semicolons: ts.SemicolonPreference.Remove }); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "function_scope_0", + actionDescription: "Extract to function in global scope", + newContent: +`/*RENAME*/newFunction() + +function newFunction() { + console.log(0) // + console.log(0) +} +` +}); diff --git a/tests/cases/fourslash/extract-method30.ts b/tests/cases/fourslash/extract-method30.ts new file mode 100644 index 0000000000000..cf2c87e5c3f88 --- /dev/null +++ b/tests/cases/fourslash/extract-method30.ts @@ -0,0 +1,20 @@ +/// + +/////*a*/console.log(0) // +////console.log(0)/*b*/ + +format.setFormatOptions({ ...format.copyFormatOptions(), semicolons: ts.SemicolonPreference.Ignore }); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "function_scope_0", + actionDescription: "Extract to function in global scope", + newContent: +`/*RENAME*/newFunction() + +function newFunction() { + console.log(0) // + console.log(0) +} +` +}); diff --git a/tests/cases/fourslash/formatAddSemicolons1.ts b/tests/cases/fourslash/formatAddSemicolons1.ts new file mode 100644 index 0000000000000..304ec32f8baf4 --- /dev/null +++ b/tests/cases/fourslash/formatAddSemicolons1.ts @@ -0,0 +1,53 @@ +/// + +////console.log(1) +////console.log(2) +////const x = function() { } +////for (let i = 0; i < 1; i++) { +//// 1 +//// 2 +////} +////do { } while (false) console.log(3) +////function f() { } +////class C { +//// ["one"] = {} +//// ["two"] +//// three: string +//// m() { } +//// ;["three"] = {} +//// ;["four"] +////} +////enum E { +//// C +////} +////type M = { [K in keyof T]: any } +////declare module 'foo' { } +////declare module 'bar' +////type T = { x: string, y: number } + +format.setFormatOptions({ ...format.copyFormatOptions(), semicolons: ts.SemicolonPreference.Insert }); +format.document(); +verify.currentFileContentIs(`console.log(1); +console.log(2); +const x = function() { }; +for (let i = 0; i < 1; i++) { + 1; + 2; +} +do { } while (false); console.log(3); +function f() { } +class C { + ["one"] = {} + ["two"]; + three: string; + m() { } + ;["three"] = {} + ;["four"]; +} +enum E { + C +} +type M = { [K in keyof T]: any }; +declare module 'foo' { } +declare module 'bar'; +type T = { x: string, y: number; };`); diff --git a/tests/cases/fourslash/formatRemoveSemicolons1.ts b/tests/cases/fourslash/formatRemoveSemicolons1.ts new file mode 100644 index 0000000000000..b80ecee7edb2d --- /dev/null +++ b/tests/cases/fourslash/formatRemoveSemicolons1.ts @@ -0,0 +1,77 @@ +/// + +////; (function f() { })(); +////const a = 3; +////+ 4; +////const b = 3 +////+ 4; +////const c = 3 + +////4; +////class C { +//// prop; +//// ["p"]; +//// zero: void; +//// ["one"] = {}; +//// ["two"]; +//// ; +////} +////a; +////`b`; +////b; +////(3); +////4; +//// / regex /; +////; +////[]; +/////** blah */[0]; +////interface I { +//// new; +//// (); +//// foo; +//// (); +////} +////type T = { +//// new; +//// (); +//// foo; +//// (); +////} + +format.setFormatOptions({ ...format.copyFormatOptions(), semicolons: ts.SemicolonPreference.Remove }); +format.document(); +verify.currentFileContentIs(`; (function f() { })() +const a = 3; ++ 4 +const b = 3 + + 4 +const c = 3 + + 4 +class C { + prop + ["p"] + zero: void + ["one"] = {}; + ["two"]; + ; +} +a; +\`b\` +b; +(3) +4; +/ regex /; +; +[]; +/** blah */[0] +interface I { + new; + () + foo; + () +} +type T = { + new; + () + foo; + () +}`); \ No newline at end of file diff --git a/tests/cases/fourslash/formatRemoveSemicolons2.ts b/tests/cases/fourslash/formatRemoveSemicolons2.ts new file mode 100644 index 0000000000000..268a217e03231 --- /dev/null +++ b/tests/cases/fourslash/formatRemoveSemicolons2.ts @@ -0,0 +1,27 @@ +/// + +////namespace ts { +//// let x = 0; +//// // +//// interface I { +//// a: string; +//// /** @internal */ +//// b: string; +//// } +//// let y = 0; // +////} +////let z = 0; // + +format.setFormatOptions({ ...format.copyFormatOptions(), semicolons: ts.SemicolonPreference.Remove }); +format.document(); +verify.currentFileContentIs(`namespace ts { + let x = 0 + // + interface I { + a: string + /** @internal */ + b: string + } + let y = 0 // +} +let z = 0 //`); diff --git a/tests/cases/fourslash/formatRemoveSemicolons3.ts b/tests/cases/fourslash/formatRemoveSemicolons3.ts new file mode 100644 index 0000000000000..0987e088d5dda --- /dev/null +++ b/tests/cases/fourslash/formatRemoveSemicolons3.ts @@ -0,0 +1,11 @@ +/// + +////(type).declaredProperties = getNamedMembers(members); +////// Start with signatures at empty array in case of recursive types +////(type).declaredCallSignatures = emptyArray; + +format.setFormatOptions({ ...format.copyFormatOptions(), semicolons: ts.SemicolonPreference.Remove }); +format.document(); +verify.currentFileContentIs(`(type).declaredProperties = getNamedMembers(members); +// Start with signatures at empty array in case of recursive types +(type).declaredCallSignatures = emptyArray`); \ No newline at end of file diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index d64bc0a86617f..a1d4cab5a4da5 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -66,6 +66,12 @@ declare module ts { Smart = 2, } + enum SemicolonPreference { + Ignore = "ignore", + Insert = "insert", + Remove = "remove", + } + interface OutputFile { name: string; writeByteOrderMark: boolean; @@ -96,6 +102,11 @@ declare namespace FourSlashInterface { position: number; data?: any; } + enum IndentStyle { + None = 0, + Block = 1, + Smart = 2, + } interface EditorOptions { BaseIndentSize?: number, IndentSize: number; @@ -103,6 +114,14 @@ declare namespace FourSlashInterface { NewLineCharacter: string; ConvertTabsToSpaces: boolean; } + interface EditorSettings { + baseIndentSize?: number; + indentSize?: number; + tabSize?: number; + newLineCharacter?: string; + convertTabsToSpaces?: boolean; + indentStyle?: IndentStyle; + } interface FormatCodeOptions extends EditorOptions { InsertSpaceAfterCommaDelimiter: boolean; InsertSpaceAfterSemicolonInForStatements: boolean; @@ -119,6 +138,26 @@ declare namespace FourSlashInterface { insertSpaceBeforeTypeAnnotation: boolean; [s: string]: boolean | number | string | undefined; } + interface FormatCodeSettings extends EditorSettings { + readonly insertSpaceAfterCommaDelimiter?: boolean; + readonly insertSpaceAfterSemicolonInForStatements?: boolean; + readonly insertSpaceBeforeAndAfterBinaryOperators?: boolean; + readonly insertSpaceAfterConstructor?: boolean; + readonly insertSpaceAfterKeywordsInControlFlowStatements?: boolean; + readonly insertSpaceAfterFunctionKeywordForAnonymousFunctions?: boolean; + readonly insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis?: boolean; + readonly insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets?: boolean; + readonly insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean; + readonly insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces?: boolean; + readonly insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; + readonly insertSpaceAfterTypeAssertion?: boolean; + readonly insertSpaceBeforeFunctionParenthesis?: boolean; + readonly placeOpenBraceOnNewLineForFunctions?: boolean; + readonly placeOpenBraceOnNewLineForControlBlocks?: boolean; + readonly insertSpaceBeforeTypeAnnotation?: boolean; + readonly indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean; + readonly semicolons?: ts.SemicolonPreference; + } interface Range { fileName: string; pos: number; @@ -375,7 +414,7 @@ declare namespace FourSlashInterface { class format { document(): void; copyFormatOptions(): FormatCodeOptions; - setFormatOptions(options: FormatCodeOptions): any; + setFormatOptions(options: FormatCodeOptions | FormatCodeSettings): any; selection(startMarker: string, endMarker: string): void; onType(posMarker: string, key: string): void; setOption(name: keyof FormatCodeOptions, value: number | string | boolean): void;