• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts.formatting {
3    export interface RuleSpec {
4        readonly leftTokenRange: TokenRange;
5        readonly rightTokenRange: TokenRange;
6        readonly rule: Rule;
7    }
8
9    export function getAllRules(): RuleSpec[] {
10        const allTokens: SyntaxKind[] = [];
11        for (let token = SyntaxKind.FirstToken; token <= SyntaxKind.LastToken; token++) {
12            if (token !== SyntaxKind.EndOfFileToken) {
13                allTokens.push(token);
14            }
15        }
16        function anyTokenExcept(...tokens: SyntaxKind[]): TokenRange {
17            return { tokens: allTokens.filter(t => !tokens.some(t2 => t2 === t)), isSpecific: false };
18        }
19
20        const anyToken: TokenRange = { tokens: allTokens, isSpecific: false };
21        const anyTokenIncludingMultilineComments = tokenRangeFrom([...allTokens, SyntaxKind.MultiLineCommentTrivia]);
22        const anyTokenIncludingEOF = tokenRangeFrom([...allTokens, SyntaxKind.EndOfFileToken]);
23        const keywords = tokenRangeFromRange(SyntaxKind.FirstKeyword, SyntaxKind.LastKeyword);
24        const binaryOperators = tokenRangeFromRange(SyntaxKind.FirstBinaryOperator, SyntaxKind.LastBinaryOperator);
25        const binaryKeywordOperators = [SyntaxKind.InKeyword, SyntaxKind.InstanceOfKeyword, SyntaxKind.OfKeyword, SyntaxKind.AsKeyword, SyntaxKind.IsKeyword];
26        const unaryPrefixOperators = [SyntaxKind.PlusPlusToken, SyntaxKind.MinusMinusToken, SyntaxKind.TildeToken, SyntaxKind.ExclamationToken];
27        const unaryPrefixExpressions = [
28            SyntaxKind.NumericLiteral, SyntaxKind.BigIntLiteral, SyntaxKind.Identifier, SyntaxKind.OpenParenToken,
29            SyntaxKind.OpenBracketToken, SyntaxKind.OpenBraceToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword];
30        const unaryPreincrementExpressions = [SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword];
31        const unaryPostincrementExpressions = [SyntaxKind.Identifier, SyntaxKind.CloseParenToken, SyntaxKind.CloseBracketToken, SyntaxKind.NewKeyword];
32        const unaryPredecrementExpressions = [SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword];
33        const unaryPostdecrementExpressions = [SyntaxKind.Identifier, SyntaxKind.CloseParenToken, SyntaxKind.CloseBracketToken, SyntaxKind.NewKeyword];
34        const comments = [SyntaxKind.SingleLineCommentTrivia, SyntaxKind.MultiLineCommentTrivia];
35        const typeNames = [SyntaxKind.Identifier, ...typeKeywords];
36
37        // Place a space before open brace in a function declaration
38        // TypeScript: Function can have return types, which can be made of tons of different token kinds
39        const functionOpenBraceLeftTokenRange = anyTokenIncludingMultilineComments;
40
41        // Place a space before open brace in a TypeScript declaration that has braces as children (class, module, enum, etc)
42        const typeScriptOpenBraceLeftTokenRange = tokenRangeFrom([SyntaxKind.Identifier, SyntaxKind.MultiLineCommentTrivia, SyntaxKind.ClassKeyword, SyntaxKind.StructKeyword, SyntaxKind.ExportKeyword, SyntaxKind.ImportKeyword]);
43
44        // Place a space before open brace in a control flow construct
45        const controlOpenBraceLeftTokenRange = tokenRangeFrom([SyntaxKind.CloseParenToken, SyntaxKind.MultiLineCommentTrivia, SyntaxKind.DoKeyword, SyntaxKind.TryKeyword, SyntaxKind.FinallyKeyword, SyntaxKind.ElseKeyword]);
46
47        // These rules are higher in priority than user-configurable
48        const highPriorityCommonRules = [
49            // Leave comments alone
50            rule("IgnoreBeforeComment", anyToken, comments, anyContext, RuleAction.StopProcessingSpaceActions),
51            rule("IgnoreAfterLineComment", SyntaxKind.SingleLineCommentTrivia, anyToken, anyContext, RuleAction.StopProcessingSpaceActions),
52
53            rule("NotSpaceBeforeColon", anyToken, SyntaxKind.ColonToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext, isNotTypeAnnotationContext], RuleAction.DeleteSpace),
54            rule("SpaceAfterColon", SyntaxKind.ColonToken, anyToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.InsertSpace),
55            rule("NoSpaceBeforeQuestionMark", anyToken, SyntaxKind.QuestionToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext, isNotTypeAnnotationContext], RuleAction.DeleteSpace),
56            // insert space after '?' only when it is used in conditional operator
57            rule("SpaceAfterQuestionMarkInConditionalOperator", SyntaxKind.QuestionToken, anyToken, [isNonJsxSameLineTokenContext, isConditionalOperatorContext], RuleAction.InsertSpace),
58
59            // in other cases there should be no space between '?' and next token
60            rule("NoSpaceAfterQuestionMark", SyntaxKind.QuestionToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
61
62            rule("NoSpaceBeforeDot", anyToken, [SyntaxKind.DotToken, SyntaxKind.QuestionDotToken], [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
63            rule("NoSpaceAfterDot", [SyntaxKind.DotToken, SyntaxKind.QuestionDotToken], anyToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
64
65            rule("NoSpaceBetweenImportParenInImportType", SyntaxKind.ImportKeyword, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isImportTypeContext], RuleAction.DeleteSpace),
66
67            // Special handling of unary operators.
68            // Prefix operators generally shouldn't have a space between
69            // them and their target unary expression.
70            rule("NoSpaceAfterUnaryPrefixOperator", unaryPrefixOperators, unaryPrefixExpressions, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.DeleteSpace),
71            rule("NoSpaceAfterUnaryPreincrementOperator", SyntaxKind.PlusPlusToken, unaryPreincrementExpressions, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
72            rule("NoSpaceAfterUnaryPredecrementOperator", SyntaxKind.MinusMinusToken, unaryPredecrementExpressions, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
73            rule("NoSpaceBeforeUnaryPostincrementOperator", unaryPostincrementExpressions, SyntaxKind.PlusPlusToken, [isNonJsxSameLineTokenContext, isNotStatementConditionContext], RuleAction.DeleteSpace),
74            rule("NoSpaceBeforeUnaryPostdecrementOperator", unaryPostdecrementExpressions, SyntaxKind.MinusMinusToken, [isNonJsxSameLineTokenContext, isNotStatementConditionContext], RuleAction.DeleteSpace),
75
76            // More unary operator special-casing.
77            // DevDiv 181814: Be careful when removing leading whitespace
78            // around unary operators.  Examples:
79            //      1 - -2  --X--> 1--2
80            //      a + ++b --X--> a+++b
81            rule("SpaceAfterPostincrementWhenFollowedByAdd", SyntaxKind.PlusPlusToken, SyntaxKind.PlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace),
82            rule("SpaceAfterAddWhenFollowedByUnaryPlus", SyntaxKind.PlusToken, SyntaxKind.PlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace),
83            rule("SpaceAfterAddWhenFollowedByPreincrement", SyntaxKind.PlusToken, SyntaxKind.PlusPlusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace),
84            rule("SpaceAfterPostdecrementWhenFollowedBySubtract", SyntaxKind.MinusMinusToken, SyntaxKind.MinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace),
85            rule("SpaceAfterSubtractWhenFollowedByUnaryMinus", SyntaxKind.MinusToken, SyntaxKind.MinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace),
86            rule("SpaceAfterSubtractWhenFollowedByPredecrement", SyntaxKind.MinusToken, SyntaxKind.MinusMinusToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace),
87
88            rule("NoSpaceAfterCloseBrace", SyntaxKind.CloseBraceToken, [SyntaxKind.CommaToken, SyntaxKind.SemicolonToken], [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
89            // For functions and control block place } on a new line [multi-line rule]
90            rule("NewLineBeforeCloseBraceInBlockContext", anyTokenIncludingMultilineComments, SyntaxKind.CloseBraceToken, [isMultilineBlockContext], RuleAction.InsertNewLine),
91
92            // Space/new line after }.
93            rule("SpaceAfterCloseBrace", SyntaxKind.CloseBraceToken, anyTokenExcept(SyntaxKind.CloseParenToken), [isNonJsxSameLineTokenContext, isAfterCodeBlockContext], RuleAction.InsertSpace),
94            // Special case for (}, else) and (}, while) since else & while tokens are not part of the tree which makes SpaceAfterCloseBrace rule not applied
95            // Also should not apply to })
96            rule("SpaceBetweenCloseBraceAndElse", SyntaxKind.CloseBraceToken, SyntaxKind.ElseKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
97            rule("SpaceBetweenCloseBraceAndWhile", SyntaxKind.CloseBraceToken, SyntaxKind.WhileKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
98            rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectContext], RuleAction.DeleteSpace),
99
100            // 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];'
101            rule("SpaceAfterConditionalClosingParen", SyntaxKind.CloseParenToken, SyntaxKind.OpenBracketToken, [isControlDeclContext], RuleAction.InsertSpace),
102
103            rule("NoSpaceBetweenFunctionKeywordAndStar", SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken, [isFunctionDeclarationOrFunctionExpressionContext], RuleAction.DeleteSpace),
104            rule("SpaceAfterStarInGeneratorDeclaration", SyntaxKind.AsteriskToken, SyntaxKind.Identifier, [isFunctionDeclarationOrFunctionExpressionContext], RuleAction.InsertSpace),
105
106            rule("SpaceAfterFunctionInFuncDecl", SyntaxKind.FunctionKeyword, anyToken, [isFunctionDeclContext], RuleAction.InsertSpace),
107            // Insert new line after { and before } in multi-line contexts.
108            rule("NewLineAfterOpenBraceInBlockContext", SyntaxKind.OpenBraceToken, anyToken, [isMultilineBlockContext], RuleAction.InsertNewLine),
109
110            // 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.
111            // Though, we do extra check on the context to make sure we are dealing with get/set node. Example:
112            //      get x() {}
113            //      set x(val) {}
114            rule("SpaceAfterGetSetInMember", [SyntaxKind.GetKeyword, SyntaxKind.SetKeyword], SyntaxKind.Identifier, [isFunctionDeclContext], RuleAction.InsertSpace),
115
116            rule("NoSpaceBetweenYieldKeywordAndStar", SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken, [isNonJsxSameLineTokenContext, isYieldOrYieldStarWithOperand], RuleAction.DeleteSpace),
117            rule("SpaceBetweenYieldOrYieldStarAndOperand", [SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken], anyToken, [isNonJsxSameLineTokenContext, isYieldOrYieldStarWithOperand], RuleAction.InsertSpace),
118
119            rule("NoSpaceBetweenReturnAndSemicolon", SyntaxKind.ReturnKeyword, SyntaxKind.SemicolonToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
120            rule("SpaceAfterCertainKeywords", [SyntaxKind.VarKeyword, SyntaxKind.ThrowKeyword, SyntaxKind.NewKeyword, SyntaxKind.DeleteKeyword, SyntaxKind.ReturnKeyword, SyntaxKind.TypeOfKeyword, SyntaxKind.AwaitKeyword], anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
121            rule("SpaceAfterLetConstInVariableDeclaration", [SyntaxKind.LetKeyword, SyntaxKind.ConstKeyword], anyToken, [isNonJsxSameLineTokenContext, isStartOfVariableDeclarationList], RuleAction.InsertSpace),
122            rule("NoSpaceBeforeOpenParenInFuncCall", anyToken, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isFunctionCallOrNewContext, isPreviousTokenNotComma], RuleAction.DeleteSpace),
123
124            // Special case for binary operators (that are keywords). For these we have to add a space and shouldn't follow any user options.
125            rule("SpaceBeforeBinaryKeywordOperator", anyToken, binaryKeywordOperators, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace),
126            rule("SpaceAfterBinaryKeywordOperator", binaryKeywordOperators, anyToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace),
127
128            rule("SpaceAfterVoidOperator", SyntaxKind.VoidKeyword, anyToken, [isNonJsxSameLineTokenContext, isVoidOpContext], RuleAction.InsertSpace),
129
130            // Async-await
131            rule("SpaceBetweenAsyncAndOpenParen", SyntaxKind.AsyncKeyword, SyntaxKind.OpenParenToken, [isArrowFunctionContext, isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
132            rule("SpaceBetweenAsyncAndFunctionKeyword", SyntaxKind.AsyncKeyword, [SyntaxKind.FunctionKeyword, SyntaxKind.Identifier], [isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
133
134            // Template string
135            rule("NoSpaceBetweenTagAndTemplateString", [SyntaxKind.Identifier, SyntaxKind.CloseParenToken], [SyntaxKind.NoSubstitutionTemplateLiteral, SyntaxKind.TemplateHead], [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
136
137            // JSX opening elements
138            rule("SpaceBeforeJsxAttribute", anyToken, SyntaxKind.Identifier, [isNextTokenParentJsxAttribute, isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
139            rule("SpaceBeforeSlashInJsxOpeningElement", anyToken, SyntaxKind.SlashToken, [isJsxSelfClosingElementContext, isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
140            rule("NoSpaceBeforeGreaterThanTokenInJsxOpeningElement", SyntaxKind.SlashToken, SyntaxKind.GreaterThanToken, [isJsxSelfClosingElementContext, isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
141            rule("NoSpaceBeforeEqualInJsxAttribute", anyToken, SyntaxKind.EqualsToken, [isJsxAttributeContext, isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
142            rule("NoSpaceAfterEqualInJsxAttribute", SyntaxKind.EqualsToken, anyToken, [isJsxAttributeContext, isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
143
144            // TypeScript-specific rules
145            // Use of module as a function call. e.g.: import m2 = module("m2");
146            rule("NoSpaceAfterModuleImport", [SyntaxKind.ModuleKeyword, SyntaxKind.RequireKeyword], SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
147            // Add a space around certain TypeScript keywords
148            rule(
149                "SpaceAfterCertainTypeScriptKeywords",
150                [
151                    SyntaxKind.AbstractKeyword,
152                    SyntaxKind.ClassKeyword,
153                    SyntaxKind.StructKeyword,
154                    SyntaxKind.DeclareKeyword,
155                    SyntaxKind.DefaultKeyword,
156                    SyntaxKind.EnumKeyword,
157                    SyntaxKind.ExportKeyword,
158                    SyntaxKind.ExtendsKeyword,
159                    SyntaxKind.GetKeyword,
160                    SyntaxKind.ImplementsKeyword,
161                    SyntaxKind.ImportKeyword,
162                    SyntaxKind.InterfaceKeyword,
163                    SyntaxKind.ModuleKeyword,
164                    SyntaxKind.NamespaceKeyword,
165                    SyntaxKind.PrivateKeyword,
166                    SyntaxKind.PublicKeyword,
167                    SyntaxKind.ProtectedKeyword,
168                    SyntaxKind.ReadonlyKeyword,
169                    SyntaxKind.SetKeyword,
170                    SyntaxKind.StaticKeyword,
171                    SyntaxKind.TypeKeyword,
172                    SyntaxKind.FromKeyword,
173                    SyntaxKind.KeyOfKeyword,
174                    SyntaxKind.InferKeyword,
175                ],
176                anyToken,
177                [isNonJsxSameLineTokenContext],
178                RuleAction.InsertSpace),
179            rule(
180                "SpaceBeforeCertainTypeScriptKeywords",
181                anyToken,
182                [SyntaxKind.ExtendsKeyword, SyntaxKind.ImplementsKeyword, SyntaxKind.FromKeyword],
183                [isNonJsxSameLineTokenContext],
184                RuleAction.InsertSpace),
185            // Treat string literals in module names as identifiers, and add a space between the literal and the opening Brace braces, e.g.: module "m2" {
186            rule("SpaceAfterModuleName", SyntaxKind.StringLiteral, SyntaxKind.OpenBraceToken, [isModuleDeclContext], RuleAction.InsertSpace),
187
188            // Lambda expressions
189            rule("SpaceBeforeArrow", anyToken, SyntaxKind.EqualsGreaterThanToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
190            rule("SpaceAfterArrow", SyntaxKind.EqualsGreaterThanToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
191
192            // Optional parameters and let args
193            rule("NoSpaceAfterEllipsis", SyntaxKind.DotDotDotToken, SyntaxKind.Identifier, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
194            rule("NoSpaceAfterOptionalParameters", SyntaxKind.QuestionToken, [SyntaxKind.CloseParenToken, SyntaxKind.CommaToken], [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.DeleteSpace),
195
196            // Remove spaces in empty interface literals. e.g.: x: {}
197            rule("NoSpaceBetweenEmptyInterfaceBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectTypeContext], RuleAction.DeleteSpace),
198
199            // generics and type assertions
200            rule("NoSpaceBeforeOpenAngularBracket", typeNames, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace),
201            rule("NoSpaceBetweenCloseParenAndAngularBracket", SyntaxKind.CloseParenToken, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace),
202            rule("NoSpaceAfterOpenAngularBracket", SyntaxKind.LessThanToken, anyToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace),
203            rule("NoSpaceBeforeCloseAngularBracket", anyToken, SyntaxKind.GreaterThanToken, [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], RuleAction.DeleteSpace),
204            rule("NoSpaceAfterCloseAngularBracket",
205                SyntaxKind.GreaterThanToken,
206                [SyntaxKind.OpenParenToken, SyntaxKind.OpenBracketToken, SyntaxKind.GreaterThanToken, SyntaxKind.CommaToken],
207                [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext, isNotFunctionDeclContext /*To prevent an interference with the SpaceBeforeOpenParenInFuncDecl rule*/],
208                RuleAction.DeleteSpace),
209
210            // decorators
211            rule("SpaceBeforeAt", [SyntaxKind.CloseParenToken, SyntaxKind.Identifier], SyntaxKind.AtToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
212            rule("NoSpaceAfterAt", SyntaxKind.AtToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
213            // Insert space after @ in decorator
214            rule("SpaceAfterDecorator",
215                anyToken,
216                [
217                    SyntaxKind.AbstractKeyword,
218                    SyntaxKind.Identifier,
219                    SyntaxKind.ExportKeyword,
220                    SyntaxKind.DefaultKeyword,
221                    SyntaxKind.ClassKeyword,
222                    SyntaxKind.StructKeyword,
223                    SyntaxKind.StaticKeyword,
224                    SyntaxKind.PublicKeyword,
225                    SyntaxKind.PrivateKeyword,
226                    SyntaxKind.ProtectedKeyword,
227                    SyntaxKind.GetKeyword,
228                    SyntaxKind.SetKeyword,
229                    SyntaxKind.OpenBracketToken,
230                    SyntaxKind.AsteriskToken,
231                ],
232                [isEndOfDecoratorContextOnSameLine],
233                RuleAction.InsertSpace),
234
235            rule("NoSpaceBeforeNonNullAssertionOperator", anyToken, SyntaxKind.ExclamationToken, [isNonJsxSameLineTokenContext, isNonNullAssertionContext], RuleAction.DeleteSpace),
236            rule("NoSpaceAfterNewKeywordOnConstructorSignature", SyntaxKind.NewKeyword, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isConstructorSignatureContext], RuleAction.DeleteSpace),
237            rule("SpaceLessThanAndNonJSXTypeAnnotation", SyntaxKind.LessThanToken, SyntaxKind.LessThanToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
238        ];
239
240        // These rules are applied after high priority
241        const userConfigurableRules = [
242            // Treat constructor as an identifier in a function declaration, and remove spaces between constructor and following left parentheses
243            rule("SpaceAfterConstructor", SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterConstructor"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
244            rule("NoSpaceAfterConstructor", SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterConstructor"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
245
246            rule("SpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionEnabled("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext, isNextTokenNotCloseBracket, isNextTokenNotCloseParen], RuleAction.InsertSpace),
247            rule("NoSpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext], RuleAction.DeleteSpace),
248
249            // Insert space after function keyword for anonymous functions
250            rule("SpaceAfterAnonymousFunctionKeyword", [SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken], SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), isFunctionDeclContext], RuleAction.InsertSpace),
251            rule("NoSpaceAfterAnonymousFunctionKeyword", [SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken], SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), isFunctionDeclContext], RuleAction.DeleteSpace),
252
253            // Insert space after keywords in control flow statements
254            rule("SpaceAfterKeywordInControl", keywords, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterKeywordsInControlFlowStatements"), isControlDeclContext], RuleAction.InsertSpace),
255            rule("NoSpaceAfterKeywordInControl", keywords, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterKeywordsInControlFlowStatements"), isControlDeclContext], RuleAction.DeleteSpace),
256
257            // Insert space after opening and before closing nonempty parenthesis
258            rule("SpaceAfterOpenParen", SyntaxKind.OpenParenToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
259            rule("SpaceBeforeCloseParen", anyToken, SyntaxKind.CloseParenToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
260            rule("SpaceBetweenOpenParens", SyntaxKind.OpenParenToken, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
261            rule("NoSpaceBetweenParens", SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
262            rule("NoSpaceAfterOpenParen", SyntaxKind.OpenParenToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
263            rule("NoSpaceBeforeCloseParen", anyToken, SyntaxKind.CloseParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
264
265            // Insert space after opening and before closing nonempty brackets
266            rule("SpaceAfterOpenBracket", SyntaxKind.OpenBracketToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
267            rule("SpaceBeforeCloseBracket", anyToken, SyntaxKind.CloseBracketToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
268            rule("NoSpaceBetweenBrackets", SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
269            rule("NoSpaceAfterOpenBracket", SyntaxKind.OpenBracketToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
270            rule("NoSpaceBeforeCloseBracket", anyToken, SyntaxKind.CloseBracketToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
271
272            // Insert a space after { and before } in single-line contexts, but remove space from empty object literals {}.
273            rule("SpaceAfterOpenBrace", SyntaxKind.OpenBraceToken, anyToken, [isOptionEnabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isBraceWrappedContext], RuleAction.InsertSpace),
274            rule("SpaceBeforeCloseBrace", anyToken, SyntaxKind.CloseBraceToken, [isOptionEnabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isBraceWrappedContext], RuleAction.InsertSpace),
275            rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectContext], RuleAction.DeleteSpace),
276            rule("NoSpaceAfterOpenBrace", SyntaxKind.OpenBraceToken, anyToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
277            rule("NoSpaceBeforeCloseBrace", anyToken, SyntaxKind.CloseBraceToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
278
279            // Insert a space after opening and before closing empty brace brackets
280            rule("SpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingEmptyBraces")], RuleAction.InsertSpace),
281            rule("NoSpaceBetweenEmptyBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingEmptyBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
282
283            // Insert space after opening and before closing template string braces
284            rule("SpaceAfterTemplateHeadAndMiddle", [SyntaxKind.TemplateHead, SyntaxKind.TemplateMiddle], anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxTextContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines),
285            rule("SpaceBeforeTemplateMiddleAndTail", anyToken, [SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail], [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
286            rule("NoSpaceAfterTemplateHeadAndMiddle", [SyntaxKind.TemplateHead, SyntaxKind.TemplateMiddle], anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxTextContext], RuleAction.DeleteSpace, RuleFlags.CanDeleteNewLines),
287            rule("NoSpaceBeforeTemplateMiddleAndTail", anyToken, [SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail], [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
288
289            // No space after { and before } in JSX expression
290            rule("SpaceAfterOpenBraceInJsxExpression", SyntaxKind.OpenBraceToken, anyToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.InsertSpace),
291            rule("SpaceBeforeCloseBraceInJsxExpression", anyToken, SyntaxKind.CloseBraceToken, [isOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.InsertSpace),
292            rule("NoSpaceAfterOpenBraceInJsxExpression", SyntaxKind.OpenBraceToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.DeleteSpace),
293            rule("NoSpaceBeforeCloseBraceInJsxExpression", anyToken, SyntaxKind.CloseBraceToken, [isOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), isNonJsxSameLineTokenContext, isJsxExpressionContext], RuleAction.DeleteSpace),
294
295            // Insert space after semicolon in for statement
296            rule("SpaceAfterSemicolonInFor", SyntaxKind.SemicolonToken, anyToken, [isOptionEnabled("insertSpaceAfterSemicolonInForStatements"), isNonJsxSameLineTokenContext, isForContext], RuleAction.InsertSpace),
297            rule("NoSpaceAfterSemicolonInFor", SyntaxKind.SemicolonToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterSemicolonInForStatements"), isNonJsxSameLineTokenContext, isForContext], RuleAction.DeleteSpace),
298
299            // Insert space before and after binary operators
300            rule("SpaceBeforeBinaryOperator", anyToken, binaryOperators, [isOptionEnabled("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace),
301            rule("SpaceAfterBinaryOperator", binaryOperators, anyToken, [isOptionEnabled("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.InsertSpace),
302            rule("NoSpaceBeforeBinaryOperator", anyToken, binaryOperators, [isOptionDisabledOrUndefined("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.DeleteSpace),
303            rule("NoSpaceAfterBinaryOperator", binaryOperators, anyToken, [isOptionDisabledOrUndefined("insertSpaceBeforeAndAfterBinaryOperators"), isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.DeleteSpace),
304
305            rule("SpaceBeforeOpenParenInFuncDecl", anyToken, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceBeforeFunctionParenthesis"), isNonJsxSameLineTokenContext, isFunctionDeclContext], RuleAction.InsertSpace),
306            rule("NoSpaceBeforeOpenParenInFuncDecl", anyToken, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceBeforeFunctionParenthesis"), isNonJsxSameLineTokenContext, isFunctionDeclContext], RuleAction.DeleteSpace),
307
308            // Open Brace braces after control block
309            rule("NewLineBeforeOpenBraceInControl", controlOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForControlBlocks"), isControlDeclContext, isBeforeMultilineBlockContext], RuleAction.InsertNewLine, RuleFlags.CanDeleteNewLines),
310
311            // Open Brace braces after function
312            // TypeScript: Function can have return types, which can be made of tons of different token kinds
313            rule("NewLineBeforeOpenBraceInFunction", functionOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForFunctions"), isFunctionDeclContext, isBeforeMultilineBlockContext], RuleAction.InsertNewLine, RuleFlags.CanDeleteNewLines),
314            // Open Brace braces after TypeScript module/class/interface
315            rule("NewLineBeforeOpenBraceInTypeScriptDeclWithBlock", typeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForFunctions"), isTypeScriptDeclWithBlockContext, isBeforeMultilineBlockContext], RuleAction.InsertNewLine, RuleFlags.CanDeleteNewLines),
316
317            rule("SpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionEnabled("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.InsertSpace),
318            rule("NoSpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.DeleteSpace),
319
320            rule("SpaceBeforeTypeAnnotation", anyToken, [SyntaxKind.QuestionToken, SyntaxKind.ColonToken], [isOptionEnabled("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.InsertSpace),
321            rule("NoSpaceBeforeTypeAnnotation", anyToken, [SyntaxKind.QuestionToken, SyntaxKind.ColonToken], [isOptionDisabledOrUndefined("insertSpaceBeforeTypeAnnotation"), isNonJsxSameLineTokenContext, isTypeAnnotationContext], RuleAction.DeleteSpace),
322
323            rule("NoOptionalSemicolon", SyntaxKind.SemicolonToken, anyTokenIncludingEOF, [optionEquals("semicolons", SemicolonPreference.Remove), isSemicolonDeletionContext], RuleAction.DeleteToken),
324            rule("OptionalSemicolon", anyToken, anyTokenIncludingEOF, [optionEquals("semicolons", SemicolonPreference.Insert), isSemicolonInsertionContext], RuleAction.InsertTrailingSemicolon),
325        ];
326
327        // These rules are lower in priority than user-configurable. Rules earlier in this list have priority over rules later in the list.
328        const lowPriorityCommonRules = [
329            // Space after keyword but not before ; or : or ?
330            rule("NoSpaceBeforeSemicolon", anyToken, SyntaxKind.SemicolonToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
331
332            rule("SpaceBeforeOpenBraceInControl", controlOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForControlBlocks"), isControlDeclContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines),
333            rule("SpaceBeforeOpenBraceInFunction", functionOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForFunctions"), isFunctionDeclContext, isBeforeBlockContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines),
334            rule("SpaceBeforeOpenBraceInTypeScriptDeclWithBlock", typeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForFunctions"), isTypeScriptDeclWithBlockContext, isNotFormatOnEnter, isSameLineTokenOrBeforeBlockContext], RuleAction.InsertSpace, RuleFlags.CanDeleteNewLines),
335
336            rule("NoSpaceBeforeComma", anyToken, SyntaxKind.CommaToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
337
338            // No space before and after indexer `x[]`
339            rule("NoSpaceBeforeOpenBracket", anyTokenExcept(SyntaxKind.AsyncKeyword, SyntaxKind.CaseKeyword), SyntaxKind.OpenBracketToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace),
340            rule("NoSpaceAfterCloseBracket", SyntaxKind.CloseBracketToken, anyToken, [isNonJsxSameLineTokenContext, isNotBeforeBlockInFunctionDeclarationContext], RuleAction.DeleteSpace),
341            rule("SpaceAfterSemicolon", SyntaxKind.SemicolonToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
342
343            // Remove extra space between for and await
344            rule("SpaceBetweenForAndAwaitKeyword", SyntaxKind.ForKeyword, SyntaxKind.AwaitKeyword, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
345
346            // Add a space between statements. All keywords except (do,else,case) has open/close parens after them.
347            // So, we have a rule to add a space for [),Any], [do,Any], [else,Any], and [case,Any]
348            rule(
349                "SpaceBetweenStatements",
350                [SyntaxKind.CloseParenToken, SyntaxKind.DoKeyword, SyntaxKind.ElseKeyword, SyntaxKind.CaseKeyword],
351                anyToken,
352                [isNonJsxSameLineTokenContext, isNonJsxElementOrFragmentContext, isNotForContext],
353                RuleAction.InsertSpace),
354            // This low-pri rule takes care of "try {", "catch {" and "finally {" in case the rule SpaceBeforeOpenBraceInControl didn't execute on FormatOnEnter.
355            rule("SpaceAfterTryCatchFinally", [SyntaxKind.TryKeyword, SyntaxKind.CatchKeyword, SyntaxKind.FinallyKeyword], SyntaxKind.OpenBraceToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace),
356        ];
357
358        return [
359            ...highPriorityCommonRules,
360            ...userConfigurableRules,
361            ...lowPriorityCommonRules,
362        ];
363    }
364
365    /**
366     * A rule takes a two tokens (left/right) and a particular context
367     * for which you're meant to look at them. You then declare what should the
368     * whitespace annotation be between these tokens via the action param.
369     *
370     * @param debugName Name to print
371     * @param left The left side of the comparison
372     * @param right The right side of the comparison
373     * @param context A set of filters to narrow down the space in which this formatter rule applies
374     * @param action a declaration of the expected whitespace
375     * @param flags whether the rule deletes a line or not, defaults to no-op
376     */
377    function rule(
378        debugName: string,
379        left: SyntaxKind | readonly SyntaxKind[] | TokenRange,
380        right: SyntaxKind | readonly SyntaxKind[] | TokenRange,
381        context: readonly ContextPredicate[],
382        action: RuleAction,
383        flags: RuleFlags = RuleFlags.None,
384    ): RuleSpec {
385        return { leftTokenRange: toTokenRange(left), rightTokenRange: toTokenRange(right), rule: { debugName, context, action, flags } };
386    }
387
388    function tokenRangeFrom(tokens: readonly SyntaxKind[]): TokenRange {
389        return { tokens, isSpecific: true };
390    }
391
392    function toTokenRange(arg: SyntaxKind | readonly SyntaxKind[] | TokenRange): TokenRange {
393        return typeof arg === "number" ? tokenRangeFrom([arg]) : isArray(arg) ? tokenRangeFrom(arg) : arg;
394    }
395
396    function tokenRangeFromRange(from: SyntaxKind, to: SyntaxKind, except: readonly SyntaxKind[] = []): TokenRange {
397        const tokens: SyntaxKind[] = [];
398        for (let token = from; token <= to; token++) {
399            if (!contains(except, token)) {
400                tokens.push(token);
401            }
402        }
403        return tokenRangeFrom(tokens);
404    }
405
406    ///
407    /// Contexts
408    ///
409
410    function optionEquals<K extends keyof FormatCodeSettings>(optionName: K, optionValue: FormatCodeSettings[K]): (context: FormattingContext) => boolean {
411        return (context) => context.options && context.options[optionName] === optionValue;
412    }
413
414    function isOptionEnabled(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean {
415        return (context) => context.options && context.options.hasOwnProperty(optionName) && !!context.options[optionName];
416    }
417
418    function isOptionDisabled(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean {
419        return (context) => context.options && context.options.hasOwnProperty(optionName) && !context.options[optionName];
420    }
421
422    function isOptionDisabledOrUndefined(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean {
423        return (context) => !context.options || !context.options.hasOwnProperty(optionName) || !context.options[optionName];
424    }
425
426    function isOptionDisabledOrUndefinedOrTokensOnSameLine(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean {
427        return (context) => !context.options || !context.options.hasOwnProperty(optionName) || !context.options[optionName] || context.TokensAreOnSameLine();
428    }
429
430    function isOptionEnabledOrUndefined(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean {
431        return (context) => !context.options || !context.options.hasOwnProperty(optionName) || !!context.options[optionName];
432    }
433
434    function isForContext(context: FormattingContext): boolean {
435        return context.contextNode.kind === SyntaxKind.ForStatement;
436    }
437
438    function isNotForContext(context: FormattingContext): boolean {
439        return !isForContext(context);
440    }
441
442    function isBinaryOpContext(context: FormattingContext): boolean {
443        switch (context.contextNode.kind) {
444            case SyntaxKind.BinaryExpression:
445                return (<BinaryExpression>context.contextNode).operatorToken.kind !== SyntaxKind.CommaToken;
446            case SyntaxKind.ConditionalExpression:
447            case SyntaxKind.ConditionalType:
448            case SyntaxKind.AsExpression:
449            case SyntaxKind.ExportSpecifier:
450            case SyntaxKind.ImportSpecifier:
451            case SyntaxKind.TypePredicate:
452            case SyntaxKind.UnionType:
453            case SyntaxKind.IntersectionType:
454                return true;
455
456            // equals in binding elements: function foo([[x, y] = [1, 2]])
457            case SyntaxKind.BindingElement:
458            // equals in type X = ...
459            // falls through
460            case SyntaxKind.TypeAliasDeclaration:
461            // equal in import a = module('a');
462            // falls through
463            case SyntaxKind.ImportEqualsDeclaration:
464            // equal in let a = 0
465            // falls through
466            case SyntaxKind.VariableDeclaration:
467            // equal in p = 0
468            // falls through
469            case SyntaxKind.Parameter:
470            case SyntaxKind.EnumMember:
471            case SyntaxKind.PropertyDeclaration:
472            case SyntaxKind.PropertySignature:
473                return context.currentTokenSpan.kind === SyntaxKind.EqualsToken || context.nextTokenSpan.kind === SyntaxKind.EqualsToken;
474            // "in" keyword in for (let x in []) { }
475            case SyntaxKind.ForInStatement:
476            // "in" keyword in [P in keyof T]: T[P]
477            // falls through
478            case SyntaxKind.TypeParameter:
479                return context.currentTokenSpan.kind === SyntaxKind.InKeyword || context.nextTokenSpan.kind === SyntaxKind.InKeyword || context.currentTokenSpan.kind === SyntaxKind.EqualsToken || context.nextTokenSpan.kind === SyntaxKind.EqualsToken;
480            // Technically, "of" is not a binary operator, but format it the same way as "in"
481            case SyntaxKind.ForOfStatement:
482                return context.currentTokenSpan.kind === SyntaxKind.OfKeyword || context.nextTokenSpan.kind === SyntaxKind.OfKeyword;
483        }
484        return false;
485    }
486
487    function isNotBinaryOpContext(context: FormattingContext): boolean {
488        return !isBinaryOpContext(context);
489    }
490
491    function isNotTypeAnnotationContext(context: FormattingContext): boolean {
492        return !isTypeAnnotationContext(context);
493    }
494
495    function isTypeAnnotationContext(context: FormattingContext): boolean {
496        const contextKind = context.contextNode.kind;
497        return contextKind === SyntaxKind.PropertyDeclaration ||
498            contextKind === SyntaxKind.PropertySignature ||
499            contextKind === SyntaxKind.Parameter ||
500            contextKind === SyntaxKind.VariableDeclaration ||
501            isFunctionLikeKind(contextKind);
502    }
503
504    function isConditionalOperatorContext(context: FormattingContext): boolean {
505        return context.contextNode.kind === SyntaxKind.ConditionalExpression ||
506                context.contextNode.kind === SyntaxKind.ConditionalType;
507    }
508
509    function isSameLineTokenOrBeforeBlockContext(context: FormattingContext): boolean {
510        return context.TokensAreOnSameLine() || isBeforeBlockContext(context);
511    }
512
513    function isBraceWrappedContext(context: FormattingContext): boolean {
514        return context.contextNode.kind === SyntaxKind.ObjectBindingPattern ||
515            context.contextNode.kind === SyntaxKind.MappedType ||
516            isSingleLineBlockContext(context);
517    }
518
519    // This check is done before an open brace in a control construct, a function, or a typescript block declaration
520    function isBeforeMultilineBlockContext(context: FormattingContext): boolean {
521        return isBeforeBlockContext(context) && !(context.NextNodeAllOnSameLine() || context.NextNodeBlockIsOnOneLine());
522    }
523
524    function isMultilineBlockContext(context: FormattingContext): boolean {
525        return isBlockContext(context) && !(context.ContextNodeAllOnSameLine() || context.ContextNodeBlockIsOnOneLine());
526    }
527
528    function isSingleLineBlockContext(context: FormattingContext): boolean {
529        return isBlockContext(context) && (context.ContextNodeAllOnSameLine() || context.ContextNodeBlockIsOnOneLine());
530    }
531
532    function isBlockContext(context: FormattingContext): boolean {
533        return nodeIsBlockContext(context.contextNode);
534    }
535
536    function isBeforeBlockContext(context: FormattingContext): boolean {
537        return nodeIsBlockContext(context.nextTokenParent);
538    }
539
540    // IMPORTANT!!! This method must return true ONLY for nodes with open and close braces as immediate children
541    function nodeIsBlockContext(node: Node): boolean {
542        if (nodeIsTypeScriptDeclWithBlockContext(node)) {
543            // This means we are in a context that looks like a block to the user, but in the grammar is actually not a node (it's a class, module, enum, object type literal, etc).
544            return true;
545        }
546
547        switch (node.kind) {
548            case SyntaxKind.Block:
549            case SyntaxKind.CaseBlock:
550            case SyntaxKind.ObjectLiteralExpression:
551            case SyntaxKind.ModuleBlock:
552                return true;
553        }
554
555        return false;
556    }
557
558    function isFunctionDeclContext(context: FormattingContext): boolean {
559        switch (context.contextNode.kind) {
560            case SyntaxKind.FunctionDeclaration:
561            case SyntaxKind.MethodDeclaration:
562            case SyntaxKind.MethodSignature:
563            // case SyntaxKind.MemberFunctionDeclaration:
564            // falls through
565            case SyntaxKind.GetAccessor:
566            case SyntaxKind.SetAccessor:
567            // case SyntaxKind.MethodSignature:
568            // falls through
569            case SyntaxKind.CallSignature:
570            case SyntaxKind.FunctionExpression:
571            case SyntaxKind.Constructor:
572            case SyntaxKind.ArrowFunction:
573            // case SyntaxKind.ConstructorDeclaration:
574            // case SyntaxKind.SimpleArrowFunctionExpression:
575            // case SyntaxKind.ParenthesizedArrowFunctionExpression:
576            // falls through
577            case SyntaxKind.InterfaceDeclaration: // This one is not truly a function, but for formatting purposes, it acts just like one
578                return true;
579        }
580
581        return false;
582    }
583
584    function isNotFunctionDeclContext(context: FormattingContext): boolean {
585        return !isFunctionDeclContext(context);
586    }
587
588    function isFunctionDeclarationOrFunctionExpressionContext(context: FormattingContext): boolean {
589        return context.contextNode.kind === SyntaxKind.FunctionDeclaration || context.contextNode.kind === SyntaxKind.FunctionExpression;
590    }
591
592    function isTypeScriptDeclWithBlockContext(context: FormattingContext): boolean {
593        return nodeIsTypeScriptDeclWithBlockContext(context.contextNode);
594    }
595
596    function nodeIsTypeScriptDeclWithBlockContext(node: Node): boolean {
597        switch (node.kind) {
598            case SyntaxKind.ClassDeclaration:
599            case SyntaxKind.StructDeclaration:
600            case SyntaxKind.ClassExpression:
601            case SyntaxKind.InterfaceDeclaration:
602            case SyntaxKind.EnumDeclaration:
603            case SyntaxKind.TypeLiteral:
604            case SyntaxKind.ModuleDeclaration:
605            case SyntaxKind.ExportDeclaration:
606            case SyntaxKind.NamedExports:
607            case SyntaxKind.ImportDeclaration:
608            case SyntaxKind.NamedImports:
609                return true;
610        }
611
612        return false;
613    }
614
615    function isAfterCodeBlockContext(context: FormattingContext): boolean {
616        switch (context.currentTokenParent.kind) {
617            case SyntaxKind.ClassDeclaration:
618            case SyntaxKind.StructDeclaration:
619            case SyntaxKind.ModuleDeclaration:
620            case SyntaxKind.EnumDeclaration:
621            case SyntaxKind.CatchClause:
622            case SyntaxKind.ModuleBlock:
623            case SyntaxKind.SwitchStatement:
624                return true;
625            case SyntaxKind.Block: {
626                const blockParent = context.currentTokenParent.parent;
627                // In a codefix scenario, we can't rely on parents being set. So just always return true.
628                if (!blockParent || blockParent.kind !== SyntaxKind.ArrowFunction && blockParent.kind !== SyntaxKind.FunctionExpression) {
629                    return true;
630                }
631            }
632        }
633        return false;
634    }
635
636    function isControlDeclContext(context: FormattingContext): boolean {
637        switch (context.contextNode.kind) {
638            case SyntaxKind.IfStatement:
639            case SyntaxKind.SwitchStatement:
640            case SyntaxKind.ForStatement:
641            case SyntaxKind.ForInStatement:
642            case SyntaxKind.ForOfStatement:
643            case SyntaxKind.WhileStatement:
644            case SyntaxKind.TryStatement:
645            case SyntaxKind.DoStatement:
646            case SyntaxKind.WithStatement:
647            // TODO
648            // case SyntaxKind.ElseClause:
649            // falls through
650            case SyntaxKind.CatchClause:
651                return true;
652
653            default:
654                return false;
655        }
656    }
657
658    function isObjectContext(context: FormattingContext): boolean {
659        return context.contextNode.kind === SyntaxKind.ObjectLiteralExpression;
660    }
661
662    function isFunctionCallContext(context: FormattingContext): boolean {
663        return context.contextNode.kind === SyntaxKind.CallExpression;
664    }
665
666    function isNewContext(context: FormattingContext): boolean {
667        return context.contextNode.kind === SyntaxKind.NewExpression;
668    }
669
670    function isFunctionCallOrNewContext(context: FormattingContext): boolean {
671        return isFunctionCallContext(context) || isNewContext(context);
672    }
673
674    function isPreviousTokenNotComma(context: FormattingContext): boolean {
675        return context.currentTokenSpan.kind !== SyntaxKind.CommaToken;
676    }
677
678    function isNextTokenNotCloseBracket(context: FormattingContext): boolean {
679        return context.nextTokenSpan.kind !== SyntaxKind.CloseBracketToken;
680    }
681
682    function isNextTokenNotCloseParen(context: FormattingContext): boolean {
683        return context.nextTokenSpan.kind !== SyntaxKind.CloseParenToken;
684    }
685
686    function isArrowFunctionContext(context: FormattingContext): boolean {
687        return context.contextNode.kind === SyntaxKind.ArrowFunction;
688    }
689
690    function isImportTypeContext(context: FormattingContext): boolean {
691        return context.contextNode.kind === SyntaxKind.ImportType;
692    }
693
694    function isNonJsxSameLineTokenContext(context: FormattingContext): boolean {
695        return context.TokensAreOnSameLine() && context.contextNode.kind !== SyntaxKind.JsxText;
696    }
697
698    function isNonJsxTextContext(context: FormattingContext): boolean {
699        return context.contextNode.kind !== SyntaxKind.JsxText;
700    }
701
702    function isNonJsxElementOrFragmentContext(context: FormattingContext): boolean {
703        return context.contextNode.kind !== SyntaxKind.JsxElement && context.contextNode.kind !== SyntaxKind.JsxFragment;
704    }
705
706    function isJsxExpressionContext(context: FormattingContext): boolean {
707        return context.contextNode.kind === SyntaxKind.JsxExpression || context.contextNode.kind === SyntaxKind.JsxSpreadAttribute;
708    }
709
710    function isNextTokenParentJsxAttribute(context: FormattingContext): boolean {
711        return context.nextTokenParent.kind === SyntaxKind.JsxAttribute;
712    }
713
714    function isJsxAttributeContext(context: FormattingContext): boolean {
715        return context.contextNode.kind === SyntaxKind.JsxAttribute;
716    }
717
718    function isJsxSelfClosingElementContext(context: FormattingContext): boolean {
719        return context.contextNode.kind === SyntaxKind.JsxSelfClosingElement;
720    }
721
722    function isNotBeforeBlockInFunctionDeclarationContext(context: FormattingContext): boolean {
723        return !isFunctionDeclContext(context) && !isBeforeBlockContext(context);
724    }
725
726    function isEndOfDecoratorContextOnSameLine(context: FormattingContext): boolean {
727        return context.TokensAreOnSameLine() &&
728            !!context.contextNode.decorators &&
729            nodeIsInDecoratorContext(context.currentTokenParent) &&
730            !nodeIsInDecoratorContext(context.nextTokenParent);
731    }
732
733    function nodeIsInDecoratorContext(node: Node): boolean {
734        while (isExpressionNode(node)) {
735            node = node.parent;
736        }
737        return node.kind === SyntaxKind.Decorator;
738    }
739
740    function isStartOfVariableDeclarationList(context: FormattingContext): boolean {
741        return context.currentTokenParent.kind === SyntaxKind.VariableDeclarationList &&
742            context.currentTokenParent.getStart(context.sourceFile) === context.currentTokenSpan.pos;
743    }
744
745    function isNotFormatOnEnter(context: FormattingContext): boolean {
746        return context.formattingRequestKind !== FormattingRequestKind.FormatOnEnter;
747    }
748
749    function isModuleDeclContext(context: FormattingContext): boolean {
750        return context.contextNode.kind === SyntaxKind.ModuleDeclaration;
751    }
752
753    function isObjectTypeContext(context: FormattingContext): boolean {
754        return context.contextNode.kind === SyntaxKind.TypeLiteral; // && context.contextNode.parent.kind !== SyntaxKind.InterfaceDeclaration;
755    }
756
757    function isConstructorSignatureContext(context: FormattingContext): boolean {
758        return context.contextNode.kind === SyntaxKind.ConstructSignature;
759    }
760
761    function isTypeArgumentOrParameterOrAssertion(token: TextRangeWithKind, parent: Node): boolean {
762        if (token.kind !== SyntaxKind.LessThanToken && token.kind !== SyntaxKind.GreaterThanToken) {
763            return false;
764        }
765        switch (parent.kind) {
766            case SyntaxKind.TypeReference:
767            case SyntaxKind.TypeAssertionExpression:
768            case SyntaxKind.TypeAliasDeclaration:
769            case SyntaxKind.ClassDeclaration:
770            case SyntaxKind.ClassExpression:
771            case SyntaxKind.StructDeclaration:
772            case SyntaxKind.InterfaceDeclaration:
773            case SyntaxKind.FunctionDeclaration:
774            case SyntaxKind.FunctionExpression:
775            case SyntaxKind.ArrowFunction:
776            case SyntaxKind.MethodDeclaration:
777            case SyntaxKind.MethodSignature:
778            case SyntaxKind.CallSignature:
779            case SyntaxKind.ConstructSignature:
780            case SyntaxKind.CallExpression:
781            case SyntaxKind.NewExpression:
782            case SyntaxKind.ExpressionWithTypeArguments:
783                return true;
784            default:
785                return false;
786
787        }
788    }
789
790    function isTypeArgumentOrParameterOrAssertionContext(context: FormattingContext): boolean {
791        return isTypeArgumentOrParameterOrAssertion(context.currentTokenSpan, context.currentTokenParent) ||
792            isTypeArgumentOrParameterOrAssertion(context.nextTokenSpan, context.nextTokenParent);
793    }
794
795    function isTypeAssertionContext(context: FormattingContext): boolean {
796        return context.contextNode.kind === SyntaxKind.TypeAssertionExpression;
797    }
798
799    function isVoidOpContext(context: FormattingContext): boolean {
800        return context.currentTokenSpan.kind === SyntaxKind.VoidKeyword && context.currentTokenParent.kind === SyntaxKind.VoidExpression;
801    }
802
803    function isYieldOrYieldStarWithOperand(context: FormattingContext): boolean {
804        return context.contextNode.kind === SyntaxKind.YieldExpression && (<YieldExpression>context.contextNode).expression !== undefined;
805    }
806
807    function isNonNullAssertionContext(context: FormattingContext): boolean {
808        return context.contextNode.kind === SyntaxKind.NonNullExpression;
809    }
810
811    function isNotStatementConditionContext(context: FormattingContext): boolean {
812        return !isStatementConditionContext(context);
813    }
814
815    function isStatementConditionContext(context: FormattingContext): boolean {
816        switch (context.contextNode.kind) {
817            case SyntaxKind.IfStatement:
818            case SyntaxKind.ForStatement:
819            case SyntaxKind.ForInStatement:
820            case SyntaxKind.ForOfStatement:
821            case SyntaxKind.DoStatement:
822            case SyntaxKind.WhileStatement:
823                return true;
824
825            default:
826                return false;
827        }
828    }
829
830    function isSemicolonDeletionContext(context: FormattingContext): boolean {
831        let nextTokenKind = context.nextTokenSpan.kind;
832        let nextTokenStart = context.nextTokenSpan.pos;
833        if (isTrivia(nextTokenKind)) {
834            const nextRealToken = context.nextTokenParent === context.currentTokenParent
835                ? findNextToken(
836                    context.currentTokenParent,
837                    findAncestor(context.currentTokenParent, a => !a.parent)!,
838                    context.sourceFile)
839                : context.nextTokenParent.getFirstToken(context.sourceFile);
840            if (!nextRealToken) {
841                return true;
842            }
843            nextTokenKind = nextRealToken.kind;
844            nextTokenStart = nextRealToken.getStart(context.sourceFile);
845        }
846
847        const startLine = context.sourceFile.getLineAndCharacterOfPosition(context.currentTokenSpan.pos).line;
848        const endLine = context.sourceFile.getLineAndCharacterOfPosition(nextTokenStart).line;
849        if (startLine === endLine) {
850            return nextTokenKind === SyntaxKind.CloseBraceToken
851                || nextTokenKind === SyntaxKind.EndOfFileToken;
852        }
853
854        if (nextTokenKind === SyntaxKind.SemicolonClassElement ||
855            nextTokenKind === SyntaxKind.SemicolonToken
856        ) {
857            return false;
858        }
859
860        if (context.contextNode.kind === SyntaxKind.InterfaceDeclaration ||
861            context.contextNode.kind === SyntaxKind.TypeAliasDeclaration
862        ) {
863            // Can’t remove semicolon after `foo`; it would parse as a method declaration:
864            //
865            // interface I {
866            //   foo;
867            //   (): void
868            // }
869            return !isPropertySignature(context.currentTokenParent)
870                || !!context.currentTokenParent.type
871                || nextTokenKind !== SyntaxKind.OpenParenToken;
872        }
873
874        if (isPropertyDeclaration(context.currentTokenParent)) {
875            return !context.currentTokenParent.initializer;
876        }
877
878        return context.currentTokenParent.kind !== SyntaxKind.ForStatement
879            && context.currentTokenParent.kind !== SyntaxKind.EmptyStatement
880            && context.currentTokenParent.kind !== SyntaxKind.SemicolonClassElement
881            && nextTokenKind !== SyntaxKind.OpenBracketToken
882            && nextTokenKind !== SyntaxKind.OpenParenToken
883            && nextTokenKind !== SyntaxKind.PlusToken
884            && nextTokenKind !== SyntaxKind.MinusToken
885            && nextTokenKind !== SyntaxKind.SlashToken
886            && nextTokenKind !== SyntaxKind.RegularExpressionLiteral
887            && nextTokenKind !== SyntaxKind.CommaToken
888            && nextTokenKind !== SyntaxKind.TemplateExpression
889            && nextTokenKind !== SyntaxKind.TemplateHead
890            && nextTokenKind !== SyntaxKind.NoSubstitutionTemplateLiteral
891            && nextTokenKind !== SyntaxKind.DotToken;
892    }
893
894    function isSemicolonInsertionContext(context: FormattingContext): boolean {
895        return positionIsASICandidate(context.currentTokenSpan.end, context.currentTokenParent, context.sourceFile);
896    }
897}
898