Add semicolon preference to formatter options (#33402)

* Add UserPreferences for semicolons

* Prototype formatter semicolon removal

* Implement semicolon insertion

* Fix existing tests

* Start adding tests

* Fix some edge cases of semicolon deletion

* Fix semicolon removal before comments

* Fix indentation

* Test on checker

* Replace semicolon-omitting writer with formatter preference

* Fix writing new nodes, update protocol

* Rename option

* Really fix formatting synthetic nodes

* Fix refactoring misses

* Un-update submodules gahhhh

* Update APIs

* Update for ESLint

* Revert accidental test change

* De-kludge deduplication of EOF processing

* Omit last element semicolon from single-line object-like types

* Revert "Omit last element semicolon from single-line object-like types"

This reverts commit 5625cb0237.

* Fix straggler test

* Add test for leading semicolon class members

* Rename a lot of stuff for clarity

* Invert some boolean logic
This commit is contained in:
Andrew Branch
2019-09-26 13:23:29 -07:00
committed by GitHub
parent 3dd7b84ef8
commit 7d9b22eea0
29 changed files with 816 additions and 256 deletions
+28 -2
View File
@@ -329,6 +329,21 @@ namespace ts {
return undefined;
}
/**
* Like `forEach`, but iterates in reverse order.
*/
export function forEachRight<T, U>(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<T, U>(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<T>(f: (arg: T) => boolean, g: (arg: T) => boolean): (arg: T) => boolean {
return arg => f(arg) || g(arg);
export function or<T extends unknown>(...fs: ((arg: T) => boolean)[]): (arg: T) => boolean {
return arg => {
for (const f of fs) {
if (f(arg)) {
return true;
}
}
return false;
};
}
export function not<T extends unknown[]>(fn: (...args: T) => boolean): (...args: T) => boolean {
return (...args) => !fn(...args);
}
export function assertType<T>(_: T): void { }
+1 -19
View File
@@ -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();
},
};
}
+12 -22
View File
@@ -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);
+7
View File
@@ -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 {
+1 -1
View File
@@ -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 {
+78 -39
View File
@@ -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<T extends SyntaxKind = SyntaxKind> extends TextRange {
kind: T;
}
export interface TextRangeWithTriviaKind extends TextRange {
kind: TriviaKind;
}
export type TextRangeWithTriviaKind = TextRangeWithKind<TriviaKind>;
export interface TokenInfo {
leadingTrivia: TextRangeWithTriviaKind[] | undefined;
@@ -19,6 +17,16 @@ namespace ts.formatting {
trailingTrivia: TextRangeWithTriviaKind[] | undefined;
}
export function createTextRangeWithKind<T extends SyntaxKind>(pos: number, end: number, kind: T): TextRangeWithKind<T> {
const textRangeWithKind: TextRangeWithKind<T> = { 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;
+1 -1
View File
@@ -99,4 +99,4 @@ namespace ts.formatting {
return false;
}
}
}
}
+24 -10
View File
@@ -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<SyntaxKind.EndOfFileToken> {
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
+12 -5
View File
@@ -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;
}
}
}
+213 -129
View File
@@ -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("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.Delete),
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<K extends keyof FormatCodeSettings>(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);
}
}
+41 -7
View File
@@ -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;
+2 -2
View File
@@ -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);
});
+2 -2
View File
@@ -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));
+2 -2
View File
@@ -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);
+16 -7
View File
@@ -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);
+12
View File
@@ -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<T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray<Node>) => T | undefined): T | undefined;
}
@@ -680,6 +684,12 @@ namespace ts {
Smart = 2,
}
export enum SemicolonPreference {
Ignore = "ignore",
Insert = "insert",
Remove = "remove",
}
/* @deprecated - consider using EditorSettings instead */
export interface EditorOptions {
BaseIndentSize?: number;
@@ -738,6 +748,7 @@ namespace ts {
readonly placeOpenBraceOnNewLineForControlBlocks?: boolean;
readonly insertSpaceBeforeTypeAnnotation?: boolean;
readonly indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean;
readonly semicolons?: SemicolonPreference;
}
export function getDefaultFormatCodeSettings(newLineCharacter?: string): FormatCodeSettings {
@@ -761,6 +772,7 @@ namespace ts {
insertSpaceBeforeFunctionParenthesis: false,
placeOpenBraceOnNewLineForFunctions: false,
placeOpenBraceOnNewLineForControlBlocks: false,
semicolons: SemicolonPreference.Ignore,
};
}
+76 -5
View File
@@ -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++;
+12
View File
@@ -5298,6 +5298,11 @@ declare namespace ts {
Block = 1,
Smart = 2
}
enum SemicolonPreference {
Ignore = "ignore",
Insert = "insert",
Remove = "remove"
}
interface EditorOptions {
BaseIndentSize?: number;
IndentSize: number;
@@ -5350,6 +5355,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 {
@@ -8197,6 +8203,11 @@ declare namespace ts.server.protocol {
Block = "Block",
Smart = "Smart"
}
enum SemicolonPreference {
Ignore = "ignore",
Insert = "insert",
Remove = "remove"
}
interface EditorSettings {
baseIndentSize?: number;
indentSize?: number;
@@ -8222,6 +8233,7 @@ declare namespace ts.server.protocol {
placeOpenBraceOnNewLineForFunctions?: boolean;
placeOpenBraceOnNewLineForControlBlocks?: boolean;
insertSpaceBeforeTypeAnnotation?: boolean;
semicolons?: SemicolonPreference;
}
interface UserPreferences {
readonly disableSuggestions?: boolean;
+6
View File
@@ -5298,6 +5298,11 @@ declare namespace ts {
Block = 1,
Smart = 2
}
enum SemicolonPreference {
Ignore = "ignore",
Insert = "insert",
Remove = "remove"
}
interface EditorOptions {
BaseIndentSize?: number;
IndentSize: number;
@@ -5350,6 +5355,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 {
@@ -6,4 +6,4 @@
//// return result
//// }
verify.rangeAfterCodeFix("app: { use: (arg0: string) => any; }");
verify.rangeAfterCodeFix("app: { use: (arg0: string) => any }");
@@ -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
+20
View File
@@ -0,0 +1,20 @@
/// <reference path='fourslash.ts' />
/////*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);
}
`
});
+20
View File
@@ -0,0 +1,20 @@
/// <reference path='fourslash.ts' />
/////*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)
}
`
});
+20
View File
@@ -0,0 +1,20 @@
/// <reference path='fourslash.ts' />
/////*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)
}
`
});
@@ -0,0 +1,53 @@
/// <reference path="fourslash.ts" />
////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<T> = { [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<T> = { [K in keyof T]: any };
declare module 'foo' { }
declare module 'bar';
type T = { x: string, y: number; };`);
@@ -0,0 +1,77 @@
/// <reference path="fourslash.ts" />
////; (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;
()
}`);
@@ -0,0 +1,27 @@
/// <reference path="fourslash.ts" />
////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 //`);
@@ -0,0 +1,11 @@
/// <reference path="fourslash.ts" />
////(<InterfaceTypeWithDeclaredMembers>type).declaredProperties = getNamedMembers(members);
////// Start with signatures at empty array in case of recursive types
////(<InterfaceTypeWithDeclaredMembers>type).declaredCallSignatures = emptyArray;
format.setFormatOptions({ ...format.copyFormatOptions(), semicolons: ts.SemicolonPreference.Remove });
format.document();
verify.currentFileContentIs(`(<InterfaceTypeWithDeclaredMembers>type).declaredProperties = getNamedMembers(members);
// Start with signatures at empty array in case of recursive types
(<InterfaceTypeWithDeclaredMembers>type).declaredCallSignatures = emptyArray`);
+40 -1
View File
@@ -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;