diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 6e61b7690bc..45430b5c506 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -60,20 +60,11 @@ "../services/jsTyping.ts", "../services/formatting/formatting.ts", "../services/formatting/formattingContext.ts", - "../services/formatting/formattingRequestKind.ts", "../services/formatting/formattingScanner.ts", - "../services/formatting/references.ts", "../services/formatting/rule.ts", - "../services/formatting/ruleAction.ts", - "../services/formatting/ruleDescriptor.ts", - "../services/formatting/ruleFlag.ts", - "../services/formatting/ruleOperation.ts", - "../services/formatting/ruleOperationContext.ts", "../services/formatting/rules.ts", "../services/formatting/rulesMap.ts", - "../services/formatting/rulesProvider.ts", "../services/formatting/smartIndenter.ts", - "../services/formatting/tokenRange.ts", "../services/codeFixProvider.ts", "../services/codefixes/fixes.ts", "../services/codefixes/helpers.ts", diff --git a/src/harness/unittests/extractTestHelpers.ts b/src/harness/unittests/extractTestHelpers.ts index ae3f663aa51..a04f443c3c9 100644 --- a/src/harness/unittests/extractTestHelpers.ts +++ b/src/harness/unittests/extractTestHelpers.ts @@ -67,33 +67,27 @@ namespace ts { } export const newLineCharacter = "\n"; - export const getRuleProvider = memoize(getRuleProviderInternal); - function getRuleProviderInternal() { - const options = { - indentSize: 4, - tabSize: 4, - newLineCharacter, - convertTabsToSpaces: true, - indentStyle: ts.IndentStyle.Smart, - insertSpaceAfterConstructor: false, - insertSpaceAfterCommaDelimiter: true, - insertSpaceAfterSemicolonInForStatements: true, - insertSpaceBeforeAndAfterBinaryOperators: true, - insertSpaceAfterKeywordsInControlFlowStatements: true, - insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, - insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, - insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, - insertSpaceBeforeFunctionParenthesis: false, - placeOpenBraceOnNewLineForFunctions: false, - placeOpenBraceOnNewLineForControlBlocks: false, - }; - const rulesProvider = new formatting.RulesProvider(); - rulesProvider.ensureUpToDate(options); - return rulesProvider; - } + export const testFormatOptions: ts.FormatCodeSettings = { + indentSize: 4, + tabSize: 4, + newLineCharacter, + convertTabsToSpaces: true, + indentStyle: ts.IndentStyle.Smart, + insertSpaceAfterConstructor: false, + insertSpaceAfterCommaDelimiter: true, + insertSpaceAfterSemicolonInForStatements: true, + insertSpaceBeforeAndAfterBinaryOperators: true, + insertSpaceAfterKeywordsInControlFlowStatements: true, + insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, + insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, + insertSpaceBeforeFunctionParenthesis: false, + placeOpenBraceOnNewLineForFunctions: false, + placeOpenBraceOnNewLineForControlBlocks: false, + }; const notImplementedHost: LanguageServiceHost = { getCompilationSettings: notImplemented, @@ -133,7 +127,7 @@ namespace ts { startPosition: selectionRange.start, endPosition: selectionRange.end, host: notImplementedHost, - rulesProvider: getRuleProvider() + formatContext: formatting.getFormatContext(testFormatOptions), }; const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromBounds(selectionRange.start, selectionRange.end)); assert.equal(rangeToExtract.errors, undefined, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText); @@ -197,7 +191,7 @@ namespace ts { startPosition: selectionRange.start, endPosition: selectionRange.end, host: notImplementedHost, - rulesProvider: getRuleProvider() + formatContext: formatting.getFormatContext(testFormatOptions), }; const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromBounds(selectionRange.start, selectionRange.end)); assert.isUndefined(rangeToExtract.errors, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText); diff --git a/src/harness/unittests/textChanges.ts b/src/harness/unittests/textChanges.ts index aa0bbb253fa..ed571b37399 100644 --- a/src/harness/unittests/textChanges.ts +++ b/src/harness/unittests/textChanges.ts @@ -23,60 +23,8 @@ namespace ts { const printerOptions = { newLine: NewLineKind.LineFeed }; const newLineCharacter = getNewLineCharacter(printerOptions); - const getRuleProviderDefault = memoize(() => { - const options = { - indentSize: 4, - tabSize: 4, - newLineCharacter, - convertTabsToSpaces: true, - indentStyle: ts.IndentStyle.Smart, - insertSpaceAfterConstructor: false, - insertSpaceAfterCommaDelimiter: true, - insertSpaceAfterSemicolonInForStatements: true, - insertSpaceBeforeAndAfterBinaryOperators: true, - insertSpaceAfterKeywordsInControlFlowStatements: true, - insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, - insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, - insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, - insertSpaceBeforeFunctionParenthesis: false, - placeOpenBraceOnNewLineForFunctions: false, - placeOpenBraceOnNewLineForControlBlocks: false, - }; - const rulesProvider = new formatting.RulesProvider(); - rulesProvider.ensureUpToDate(options); - return rulesProvider; - }); - const getRuleProviderNewlineBrace = memoize(() => { - const options = { - indentSize: 4, - tabSize: 4, - newLineCharacter, - convertTabsToSpaces: true, - indentStyle: ts.IndentStyle.Smart, - insertSpaceAfterConstructor: false, - insertSpaceAfterCommaDelimiter: true, - insertSpaceAfterSemicolonInForStatements: true, - insertSpaceBeforeAndAfterBinaryOperators: true, - insertSpaceAfterKeywordsInControlFlowStatements: true, - insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, - insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, - insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, - insertSpaceBeforeFunctionParenthesis: false, - placeOpenBraceOnNewLineForFunctions: true, - placeOpenBraceOnNewLineForControlBlocks: false, - }; - const rulesProvider = new formatting.RulesProvider(); - rulesProvider.ensureUpToDate(options); - return rulesProvider; - }); - function getRuleProvider(placeOpenBraceOnNewLineForFunctions: boolean) { - return placeOpenBraceOnNewLineForFunctions ? getRuleProviderNewlineBrace() : getRuleProviderDefault(); + function getRuleProvider(placeOpenBraceOnNewLineForFunctions: boolean): formatting.FormatContext { + return formatting.getFormatContext(placeOpenBraceOnNewLineForFunctions ? { ...testFormatOptions, placeOpenBraceOnNewLineForFunctions: true } : testFormatOptions); } // validate that positions that were recovered from the printed text actually match positions that will be created if the same text is parsed. diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index e0e57801cf8..a0c7ffd75ea 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -166,7 +166,7 @@ namespace ts.codefix { return { host: context.host, newLineCharacter: context.newLineCharacter, - rulesProvider: context.rulesProvider, + formatContext: context.formatContext, sourceFile: context.sourceFile, checker, compilerOptions: context.program.getCompilerOptions(), diff --git a/src/services/completions.ts b/src/services/completions.ts index 3c233f4dbe8..f66242ca864 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -415,7 +415,7 @@ namespace ts.Completions { entryId: CompletionEntryIdentifier, allSourceFiles: ReadonlyArray, host: LanguageServiceHost, - rulesProvider: formatting.RulesProvider, + formatContext: formatting.FormatContext, ): CompletionEntryDetails { const { name, source } = entryId; // Compute all the completion symbols again. @@ -436,7 +436,7 @@ namespace ts.Completions { } case "symbol": { const { symbol, location, symbolToOriginInfoMap } = symbolCompletion; - const codeActions = getCompletionEntryCodeActions(symbolToOriginInfoMap, symbol, typeChecker, host, compilerOptions, sourceFile, rulesProvider); + const codeActions = getCompletionEntryCodeActions(symbolToOriginInfoMap, symbol, typeChecker, host, compilerOptions, sourceFile, formatContext); const kindModifiers = SymbolDisplay.getSymbolModifiers(symbol); const { displayParts, documentation, symbolKind, tags } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, sourceFile, location, location, SemanticMeaning.All); return { name, kindModifiers, kind: symbolKind, displayParts, documentation, tags, codeActions, source: source === undefined ? undefined : [textPart(source)] }; @@ -467,7 +467,7 @@ namespace ts.Completions { host: LanguageServiceHost, compilerOptions: CompilerOptions, sourceFile: SourceFile, - rulesProvider: formatting.RulesProvider, + formatContext: formatting.FormatContext, ): CodeAction[] | undefined { const symbolOriginInfo = symbolToOriginInfoMap[getSymbolId(symbol)]; if (!symbolOriginInfo) { @@ -481,7 +481,7 @@ namespace ts.Completions { newLineCharacter: host.getNewLine(), compilerOptions, sourceFile, - rulesProvider, + formatContext, symbolName: symbol.name, getCanonicalFileName: createGetCanonicalFileName(host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : false), symbolToken: undefined, diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index 529fdae0545..b8122827491 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -1,10 +1,14 @@ -/// -/// -/// -/// +/// +/// +/// +/// /* @internal */ namespace ts.formatting { + export interface FormatContext { + readonly options: ts.FormatCodeSettings; + readonly getRule: ts.formatting.RulesMap; + } export interface TextRangeWithKind extends TextRange { kind: SyntaxKind; @@ -67,7 +71,7 @@ namespace ts.formatting { delta: number; } - export function formatOnEnter(position: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeSettings): TextChange[] { + export function formatOnEnter(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { const line = sourceFile.getLineAndCharacterOfPosition(position).line; if (line === 0) { return []; @@ -93,15 +97,15 @@ namespace ts.formatting { // end value is exclusive so add 1 to the result end: endOfFormatSpan + 1 }; - return formatSpan(span, sourceFile, options, rulesProvider, FormattingRequestKind.FormatOnEnter); + return formatSpan(span, sourceFile, formatContext, FormattingRequestKind.FormatOnEnter); } - export function formatOnSemicolon(position: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeSettings): TextChange[] { + export function formatOnSemicolon(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { const semicolon = findImmediatelyPrecedingTokenOfKind(position, SyntaxKind.SemicolonToken, sourceFile); - return formatNodeLines(findOutermostNodeWithinListLevel(semicolon), sourceFile, options, rulesProvider, FormattingRequestKind.FormatOnSemicolon); + return formatNodeLines(findOutermostNodeWithinListLevel(semicolon), sourceFile, formatContext, FormattingRequestKind.FormatOnSemicolon); } - export function formatOnOpeningCurly(position: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeSettings): TextChange[] { + export function formatOnOpeningCurly(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { const openingCurly = findImmediatelyPrecedingTokenOfKind(position, SyntaxKind.OpenBraceToken, sourceFile); if (!openingCurly) { return []; @@ -126,29 +130,29 @@ namespace ts.formatting { end: position }; - return formatSpan(textRange, sourceFile, options, rulesProvider, FormattingRequestKind.FormatOnOpeningCurlyBrace); + return formatSpan(textRange, sourceFile, formatContext, FormattingRequestKind.FormatOnOpeningCurlyBrace); } - export function formatOnClosingCurly(position: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeSettings): TextChange[] { + export function formatOnClosingCurly(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { const precedingToken = findImmediatelyPrecedingTokenOfKind(position, SyntaxKind.CloseBraceToken, sourceFile); - return formatNodeLines(findOutermostNodeWithinListLevel(precedingToken), sourceFile, options, rulesProvider, FormattingRequestKind.FormatOnClosingCurlyBrace); + return formatNodeLines(findOutermostNodeWithinListLevel(precedingToken), sourceFile, formatContext, FormattingRequestKind.FormatOnClosingCurlyBrace); } - export function formatDocument(sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeSettings): TextChange[] { + export function formatDocument(sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { const span = { pos: 0, end: sourceFile.text.length }; - return formatSpan(span, sourceFile, options, rulesProvider, FormattingRequestKind.FormatDocument); + return formatSpan(span, sourceFile, formatContext, FormattingRequestKind.FormatDocument); } - export function formatSelection(start: number, end: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeSettings): TextChange[] { + export function formatSelection(start: number, end: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] { // format from the beginning of the line const span = { pos: getLineStartPositionForPosition(start, sourceFile), end, }; - return formatSpan(span, sourceFile, options, rulesProvider, FormattingRequestKind.FormatSelection); + return formatSpan(span, sourceFile, formatContext, FormattingRequestKind.FormatSelection); } /** @@ -337,7 +341,7 @@ namespace ts.formatting { } /* @internal */ - export function formatNodeGivenIndentation(node: Node, sourceFileLike: SourceFileLike, languageVariant: LanguageVariant, initialIndentation: number, delta: number, rulesProvider: RulesProvider): TextChange[] { + export function formatNodeGivenIndentation(node: Node, sourceFileLike: SourceFileLike, languageVariant: LanguageVariant, initialIndentation: number, delta: number, formatContext: FormatContext): TextChange[] { const range = { pos: 0, end: sourceFileLike.text.length }; return getFormattingScanner(sourceFileLike.text, languageVariant, range.pos, range.end, scanner => formatSpanWorker( range, @@ -345,14 +349,13 @@ namespace ts.formatting { initialIndentation, delta, scanner, - rulesProvider.getFormatOptions(), - rulesProvider, + formatContext, FormattingRequestKind.FormatSelection, _ => false, // assume that node does not have any errors sourceFileLike)); } - function formatNodeLines(node: Node, sourceFile: SourceFile, options: FormatCodeSettings, rulesProvider: RulesProvider, requestKind: FormattingRequestKind): TextChange[] { + function formatNodeLines(node: Node, sourceFile: SourceFile, formatContext: FormatContext, requestKind: FormattingRequestKind): TextChange[] { if (!node) { return []; } @@ -362,24 +365,19 @@ namespace ts.formatting { end: node.end }; - return formatSpan(span, sourceFile, options, rulesProvider, requestKind); + return formatSpan(span, sourceFile, formatContext, requestKind); } - function formatSpan(originalRange: TextRange, - sourceFile: SourceFile, - options: FormatCodeSettings, - rulesProvider: RulesProvider, - requestKind: FormattingRequestKind): TextChange[] { + function formatSpan(originalRange: TextRange, sourceFile: SourceFile, formatContext: FormatContext, requestKind: FormattingRequestKind): TextChange[] { // find the smallest node that fully wraps the range and compute the initial indentation for the node const enclosingNode = findEnclosingNode(originalRange, sourceFile); return getFormattingScanner(sourceFile.text, sourceFile.languageVariant, getScanStartPosition(enclosingNode, originalRange, sourceFile), originalRange.end, scanner => formatSpanWorker( originalRange, enclosingNode, - SmartIndenter.getIndentationForNode(enclosingNode, originalRange, sourceFile, options), - getOwnOrInheritedDelta(enclosingNode, options, sourceFile), + SmartIndenter.getIndentationForNode(enclosingNode, originalRange, sourceFile, formatContext.options), + getOwnOrInheritedDelta(enclosingNode, formatContext.options, sourceFile), scanner, - options, - rulesProvider, + formatContext, requestKind, prepareRangeContainsErrorFunction(sourceFile.parseDiagnostics, originalRange), sourceFile)); @@ -390,8 +388,7 @@ namespace ts.formatting { initialIndentation: number, delta: number, formattingScanner: FormattingScanner, - options: FormatCodeSettings, - rulesProvider: RulesProvider, + { options, getRule }: FormatContext, requestKind: FormattingRequestKind, rangeContainsError: (r: TextRange) => boolean, sourceFile: SourceFileLike): TextChange[] { @@ -917,14 +914,14 @@ namespace ts.formatting { formattingContext.updateContext(previousItem, previousParent, currentItem, currentParent, contextNode); - const rule = rulesProvider.getRulesMap().GetRule(formattingContext); + const rule = getRule(formattingContext); let trimTrailingWhitespaces: boolean; let lineAdded: boolean; if (rule) { applyRuleEdits(rule, previousItem, previousStartLine, currentItem, currentStartLine); - if (rule.operation.action & (RuleAction.Space | RuleAction.Delete) && currentStartLine !== previousStartLine) { + if (rule.action & (RuleAction.Space | RuleAction.Delete) && currentStartLine !== previousStartLine) { lineAdded = false; // 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. @@ -932,7 +929,7 @@ namespace ts.formatting { dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ false); } } - else if (rule.operation.action & RuleAction.NewLine && currentStartLine === previousStartLine) { + else if (rule.action & RuleAction.NewLine && currentStartLine === previousStartLine) { lineAdded = true; // Handle the case where token2 is moved to the new line. // In this case we indent token2 in the next pass but we set @@ -943,7 +940,7 @@ namespace ts.formatting { } // 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.operation.action & RuleAction.Delete) && rule.flag !== RuleFlags.CanDeleteNewLines; + trimTrailingWhitespaces = !(rule.action & RuleAction.Delete) && rule.flags !== RuleFlags.CanDeleteNewLines; } else { trimTrailingWhitespaces = true; @@ -1118,7 +1115,7 @@ namespace ts.formatting { currentRange: TextRangeWithKind, currentStartLine: number): void { - switch (rule.operation.action) { + switch (rule.action) { case RuleAction.Ignore: // no action required return; @@ -1132,7 +1129,7 @@ namespace ts.formatting { // 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 - if (rule.flag !== RuleFlags.CanDeleteNewLines && previousStartLine !== currentStartLine) { + if (rule.flags !== RuleFlags.CanDeleteNewLines && previousStartLine !== currentStartLine) { return; } @@ -1144,7 +1141,7 @@ namespace ts.formatting { break; case RuleAction.Space: // exit early if we on different lines and rule cannot change number of newlines - if (rule.flag !== RuleFlags.CanDeleteNewLines && previousStartLine !== currentStartLine) { + if (rule.flags !== RuleFlags.CanDeleteNewLines && previousStartLine !== currentStartLine) { return; } diff --git a/src/services/formatting/formattingContext.ts b/src/services/formatting/formattingContext.ts index 043df72f434..2ba987e4af3 100644 --- a/src/services/formatting/formattingContext.ts +++ b/src/services/formatting/formattingContext.ts @@ -1,7 +1,14 @@ -/// - /* @internal */ namespace ts.formatting { + export const enum FormattingRequestKind { + FormatDocument, + FormatSelection, + FormatOnEnter, + FormatOnSemicolon, + FormatOnOpeningCurlyBrace, + FormatOnClosingCurlyBrace + } + export class FormattingContext { public currentTokenSpan: TextRangeWithKind; public nextTokenSpan: TextRangeWithKind; diff --git a/src/services/formatting/formattingRequestKind.ts b/src/services/formatting/formattingRequestKind.ts deleted file mode 100644 index 6c671e1b888..00000000000 --- a/src/services/formatting/formattingRequestKind.ts +++ /dev/null @@ -1,13 +0,0 @@ -/// - -/* @internal */ -namespace ts.formatting { - export const enum FormattingRequestKind { - FormatDocument, - FormatSelection, - FormatOnEnter, - FormatOnSemicolon, - FormatOnOpeningCurlyBrace, - FormatOnClosingCurlyBrace - } -} \ No newline at end of file diff --git a/src/services/formatting/references.ts b/src/services/formatting/references.ts deleted file mode 100644 index 318f10c664e..00000000000 --- a/src/services/formatting/references.ts +++ /dev/null @@ -1,12 +0,0 @@ -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// \ No newline at end of file diff --git a/src/services/formatting/rule.ts b/src/services/formatting/rule.ts index 8fd432586b4..aa1bb6b43e9 100644 --- a/src/services/formatting/rule.ts +++ b/src/services/formatting/rule.ts @@ -1,14 +1,30 @@ -/// - /* @internal */ namespace ts.formatting { - export class Rule { + export interface Rule { // Used for debugging to identify each rule based on the property name it's assigned to. - public debugName?: string; - constructor( - readonly descriptor: RuleDescriptor, - readonly operation: RuleOperation, - readonly flag: RuleFlags = RuleFlags.None) { - } + readonly debugName: string; + readonly context: ReadonlyArray; + readonly action: RuleAction; + readonly flags: RuleFlags; + } + + export type ContextPredicate = (context: FormattingContext) => boolean; + export const anyContext: ReadonlyArray = emptyArray; + + export const enum RuleAction { + Ignore = 1 << 0, + Space = 1 << 1, + NewLine = 1 << 2, + Delete = 1 << 3, + } + + export const enum RuleFlags { + None, + CanDeleteNewLines, + } + + export interface TokenRange { + readonly tokens: ReadonlyArray; + readonly isSpecific: boolean; } } \ No newline at end of file diff --git a/src/services/formatting/ruleAction.ts b/src/services/formatting/ruleAction.ts deleted file mode 100644 index 13e9043e1c6..00000000000 --- a/src/services/formatting/ruleAction.ts +++ /dev/null @@ -1,11 +0,0 @@ -/// - -/* @internal */ -namespace ts.formatting { - export const enum RuleAction { - Ignore = 0x00000001, - Space = 0x00000002, - NewLine = 0x00000004, - Delete = 0x00000008 - } -} \ No newline at end of file diff --git a/src/services/formatting/ruleDescriptor.ts b/src/services/formatting/ruleDescriptor.ts deleted file mode 100644 index b8529496956..00000000000 --- a/src/services/formatting/ruleDescriptor.ts +++ /dev/null @@ -1,30 +0,0 @@ -/// - -/* @internal */ -namespace ts.formatting { - export class RuleDescriptor { - constructor(public leftTokenRange: Shared.TokenRange, public rightTokenRange: Shared.TokenRange) { - } - - public toString(): string { - return "[leftRange=" + this.leftTokenRange + "," + - "rightRange=" + this.rightTokenRange + "]"; - } - - static create1(left: SyntaxKind, right: SyntaxKind): RuleDescriptor { - return RuleDescriptor.create4(Shared.TokenRange.FromToken(left), Shared.TokenRange.FromToken(right)); - } - - static create2(left: Shared.TokenRange, right: SyntaxKind): RuleDescriptor { - return RuleDescriptor.create4(left, Shared.TokenRange.FromToken(right)); - } - - static create3(left: SyntaxKind, right: Shared.TokenRange): RuleDescriptor { - return RuleDescriptor.create4(Shared.TokenRange.FromToken(left), right); - } - - static create4(left: Shared.TokenRange, right: Shared.TokenRange): RuleDescriptor { - return new RuleDescriptor(left, right); - } - } -} \ No newline at end of file diff --git a/src/services/formatting/ruleFlag.ts b/src/services/formatting/ruleFlag.ts deleted file mode 100644 index 7619f232ad4..00000000000 --- a/src/services/formatting/ruleFlag.ts +++ /dev/null @@ -1,10 +0,0 @@ -/// - - -/* @internal */ -namespace ts.formatting { - export const enum RuleFlags { - None, - CanDeleteNewLines - } -} \ No newline at end of file diff --git a/src/services/formatting/ruleOperation.ts b/src/services/formatting/ruleOperation.ts deleted file mode 100644 index 462c27352d8..00000000000 --- a/src/services/formatting/ruleOperation.ts +++ /dev/null @@ -1,21 +0,0 @@ -/// - -/* @internal */ -namespace ts.formatting { - export class RuleOperation { - constructor(readonly context: RuleOperationContext, readonly action: RuleAction) {} - - public toString(): string { - return "[context=" + this.context + "," + - "action=" + this.action + "]"; - } - - static create1(action: RuleAction) { - return RuleOperation.create2(RuleOperationContext.any, action); - } - - static create2(context: RuleOperationContext, action: RuleAction) { - return new RuleOperation(context, action); - } - } -} \ No newline at end of file diff --git a/src/services/formatting/ruleOperationContext.ts b/src/services/formatting/ruleOperationContext.ts deleted file mode 100644 index c433d106372..00000000000 --- a/src/services/formatting/ruleOperationContext.ts +++ /dev/null @@ -1,32 +0,0 @@ -/// - -/* @internal */ -namespace ts.formatting { - - export class RuleOperationContext { - private readonly customContextChecks: ((context: FormattingContext) => boolean)[]; - - constructor(...funcs: ((context: FormattingContext) => boolean)[]) { - this.customContextChecks = funcs; - } - - static readonly any: RuleOperationContext = new RuleOperationContext(); - - public IsAny(): boolean { - return this === RuleOperationContext.any; - } - - public InContext(context: FormattingContext): boolean { - if (this.IsAny()) { - return true; - } - - for (const check of this.customContextChecks) { - if (!check(context)) { - return false; - } - } - return true; - } - } -} \ No newline at end of file diff --git a/src/services/formatting/rules.ts b/src/services/formatting/rules.ts index c5b59e818eb..8214ffd6f2d 100644 --- a/src/services/formatting/rules.ts +++ b/src/services/formatting/rules.ts @@ -1,929 +1,723 @@ -/// - /* @internal */ namespace ts.formatting { - // tslint:disable variable-name (TODO) - export class Rules { - public IgnoreBeforeComment: Rule; - public IgnoreAfterLineComment: Rule; + export interface RuleSpec { + readonly leftTokenRange: TokenRange; + readonly rightTokenRange: TokenRange; + readonly rule: Rule; + } - // Space after keyword but not before ; or : or ? - public NoSpaceBeforeSemicolon: Rule; - public NoSpaceBeforeColon: Rule; - public NoSpaceBeforeQuestionMark: Rule; - public SpaceAfterColon: Rule; - // insert space after '?' only when it is used in conditional operator - public SpaceAfterQuestionMarkInConditionalOperator: Rule; - // in other cases there should be no space between '?' and next token - public NoSpaceAfterQuestionMark: Rule; + export function getAllRules(): RuleSpec[] { + const allTokens: SyntaxKind[] = []; + for (let token = SyntaxKind.FirstToken; token <= SyntaxKind.LastToken; token++) { + allTokens.push(token); + } + function anyTokenExcept(token: SyntaxKind): TokenRange { + return { tokens: allTokens.filter(t => t !== token), isSpecific: false }; + } - public SpaceAfterSemicolon: Rule; + const anyToken: TokenRange = { tokens: allTokens, isSpecific: false }; + const anyTokenIncludingMultilineComments = tokenRangeFrom([...allTokens, SyntaxKind.MultiLineCommentTrivia]); + 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]; + const unaryPrefixOperators = [SyntaxKind.PlusPlusToken, SyntaxKind.MinusMinusToken, SyntaxKind.TildeToken, SyntaxKind.ExclamationToken]; + const unaryPrefixExpressions = [ + SyntaxKind.NumericLiteral, SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.OpenBracketToken, + SyntaxKind.OpenBraceToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]; + const unaryPreincrementExpressions = [SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]; + const unaryPostincrementExpressions = [SyntaxKind.Identifier, SyntaxKind.CloseParenToken, SyntaxKind.CloseBracketToken, SyntaxKind.NewKeyword]; + const unaryPredecrementExpressions = [SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]; + const unaryPostdecrementExpressions = [SyntaxKind.Identifier, SyntaxKind.CloseParenToken, SyntaxKind.CloseBracketToken, SyntaxKind.NewKeyword]; + const comments = [SyntaxKind.SingleLineCommentTrivia, SyntaxKind.MultiLineCommentTrivia]; + const typeNames = [SyntaxKind.Identifier, ...typeKeywords]; - // Space/new line after }. - public SpaceAfterCloseBrace: Rule; - - // 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 }) - public SpaceBetweenCloseBraceAndElse: Rule; - public SpaceBetweenCloseBraceAndWhile: Rule; - public NoSpaceAfterCloseBrace: Rule; - - // No space for dot - public NoSpaceBeforeDot: Rule; - public NoSpaceAfterDot: Rule; - - // No space before and after indexer - public NoSpaceBeforeOpenBracket: Rule; - public NoSpaceAfterCloseBracket: Rule; - - // Insert a space after { and before } in single-line contexts, but remove space from empty object literals {}. - public SpaceAfterOpenBrace: Rule; - public SpaceBeforeCloseBrace: Rule; - public NoSpaceAfterOpenBrace: Rule; - public NoSpaceBeforeCloseBrace: Rule; - public NoSpaceBetweenEmptyBraceBrackets: Rule; - - // Insert new line after { and before } in multi-line contexts. - public NewLineAfterOpenBraceInBlockContext: Rule; - - // For functions and control block place } on a new line [multi-line rule] - public NewLineBeforeCloseBraceInBlockContext: Rule; - - // Special handling of unary operators. - // Prefix operators generally shouldn't have a space between - // them and their target unary expression. - public NoSpaceAfterUnaryPrefixOperator: Rule; - public NoSpaceAfterUnaryPreincrementOperator: Rule; - public NoSpaceAfterUnaryPredecrementOperator: Rule; - public NoSpaceBeforeUnaryPostincrementOperator: Rule; - public NoSpaceBeforeUnaryPostdecrementOperator: Rule; - - // 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 - public SpaceAfterPostincrementWhenFollowedByAdd: Rule; - public SpaceAfterAddWhenFollowedByUnaryPlus: Rule; - public SpaceAfterAddWhenFollowedByPreincrement: Rule; - public SpaceAfterPostdecrementWhenFollowedBySubtract: Rule; - public SpaceAfterSubtractWhenFollowedByUnaryMinus: Rule; - public SpaceAfterSubtractWhenFollowedByPredecrement: Rule; - - public NoSpaceBeforeComma: Rule; - - public SpaceAfterCertainKeywords: Rule; - public NoSpaceAfterNewKeywordOnConstructorSignature: Rule; - public SpaceAfterLetConstInVariableDeclaration: Rule; - public NoSpaceBeforeOpenParenInFuncCall: Rule; - public SpaceAfterFunctionInFuncDecl: Rule; - public SpaceBeforeOpenParenInFuncDecl: Rule; - public NoSpaceBeforeOpenParenInFuncDecl: Rule; - public SpaceAfterVoidOperator: Rule; - - public NoSpaceBetweenReturnAndSemicolon: Rule; - - // 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] - public SpaceBetweenStatements: Rule; - - // This low-pri rule takes care of "try {" and "finally {" in case the rule SpaceBeforeOpenBraceInControl didn't execute on FormatOnEnter. - public SpaceAfterTryFinally: Rule; - - // 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) {} - public SpaceAfterGetSetInMember: Rule; - - // Special case for binary operators (that are keywords). For these we have to add a space and shouldn't follow any user options. - public SpaceBeforeBinaryKeywordOperator: Rule; - public SpaceAfterBinaryKeywordOperator: Rule; - - // TypeScript-specific rules - - // Treat constructor as an identifier in a function declaration, and remove spaces between constructor and following left parentheses - public SpaceAfterConstructor: Rule; - public NoSpaceAfterConstructor: Rule; - - // Use of module as a function call. e.g.: import m2 = module("m2"); - public NoSpaceAfterModuleImport: Rule; - - // Add a space around certain TypeScript keywords - public SpaceAfterCertainTypeScriptKeywords: Rule; - public SpaceBeforeCertainTypeScriptKeywords: Rule; - - // Treat string literals in module names as identifiers, and add a space between the literal and the opening Brace braces, e.g.: module "m2" { - public SpaceAfterModuleName: Rule; - - // Lambda expressions - public SpaceBeforeArrow: Rule; - public SpaceAfterArrow: Rule; - - // Optional parameters and let args - public NoSpaceAfterEllipsis: Rule; - public NoSpaceAfterOptionalParameters: Rule; - - // generics - public NoSpaceBeforeOpenAngularBracket: Rule; - public NoSpaceBetweenCloseParenAndAngularBracket: Rule; - public NoSpaceAfterOpenAngularBracket: Rule; - public NoSpaceBeforeCloseAngularBracket: Rule; - public NoSpaceAfterCloseAngularBracket: Rule; - - // Remove spaces in empty interface literals. e.g.: x: {} - public NoSpaceBetweenEmptyInterfaceBraceBrackets: Rule; - - // These rules are higher in priority than user-configurable rules. - public HighPriorityCommonRules: Rule[]; - - // These rules are applied after high priority rules. - public UserConfigurableRules: Rule[]; - - // These rules are lower in priority than user-configurable rules. - public LowPriorityCommonRules: Rule[]; - - /// - /// Rules controlled by user options - /// - - // Insert space after comma delimiter - public SpaceAfterComma: Rule; - public NoSpaceAfterComma: Rule; - - // Insert space before and after binary operators - public SpaceBeforeBinaryOperator: Rule; - public SpaceAfterBinaryOperator: Rule; - public NoSpaceBeforeBinaryOperator: Rule; - public NoSpaceAfterBinaryOperator: Rule; - - // Insert space after keywords in control flow statements - public SpaceAfterKeywordInControl: Rule; - public NoSpaceAfterKeywordInControl: Rule; - - // Open Brace braces after function + // Place a space before open brace in a function declaration // TypeScript: Function can have return types, which can be made of tons of different token kinds - public FunctionOpenBraceLeftTokenRange: Shared.TokenRange; - public SpaceBeforeOpenBraceInFunction: Rule; - public NewLineBeforeOpenBraceInFunction: Rule; + const functionOpenBraceLeftTokenRange = anyTokenIncludingMultilineComments; - // Open Brace braces after TypeScript module/class/interface - public TypeScriptOpenBraceLeftTokenRange: Shared.TokenRange; - public SpaceBeforeOpenBraceInTypeScriptDeclWithBlock: Rule; - public NewLineBeforeOpenBraceInTypeScriptDeclWithBlock: Rule; + // Place a space before open brace in a TypeScript declaration that has braces as children (class, module, enum, etc) + const typeScriptOpenBraceLeftTokenRange = tokenRangeFrom([SyntaxKind.Identifier, SyntaxKind.MultiLineCommentTrivia, SyntaxKind.ClassKeyword, SyntaxKind.ExportKeyword, SyntaxKind.ImportKeyword]); - // Open Brace braces after control block - public ControlOpenBraceLeftTokenRange: Shared.TokenRange; - public SpaceBeforeOpenBraceInControl: Rule; - public NewLineBeforeOpenBraceInControl: Rule; - - // Insert space after semicolon in for statement - public SpaceAfterSemicolonInFor: Rule; - public NoSpaceAfterSemicolonInFor: Rule; - - // Insert space after opening and before closing nonempty parenthesis - public SpaceAfterOpenParen: Rule; - public SpaceBeforeCloseParen: Rule; - public SpaceBetweenOpenParens: Rule; - public NoSpaceBetweenParens: Rule; - public NoSpaceAfterOpenParen: Rule; - public NoSpaceBeforeCloseParen: Rule; - - // Insert space after opening and before closing nonempty brackets - public SpaceAfterOpenBracket: Rule; - public SpaceBeforeCloseBracket: Rule; - public NoSpaceBetweenBrackets: Rule; - public NoSpaceAfterOpenBracket: Rule; - public NoSpaceBeforeCloseBracket: Rule; - - // Insert space after function keyword for anonymous functions - public SpaceAfterAnonymousFunctionKeyword: Rule; - public NoSpaceAfterAnonymousFunctionKeyword: Rule; - - // Insert space after @ in decorator - public SpaceBeforeAt: Rule; - public NoSpaceAfterAt: Rule; - public SpaceAfterDecorator: Rule; - - // Generator: function* - public NoSpaceBetweenFunctionKeywordAndStar: Rule; - public SpaceAfterStarInGeneratorDeclaration: Rule; - public NoSpaceBetweenYieldKeywordAndStar: Rule; - public SpaceBetweenYieldOrYieldStarAndOperand: Rule; - - // Async functions - public SpaceBetweenAsyncAndOpenParen: Rule; - public SpaceBetweenAsyncAndFunctionKeyword: Rule; - - // Template strings - public NoSpaceBetweenTagAndTemplateString: Rule; - public NoSpaceAfterTemplateHeadAndMiddle: Rule; - public SpaceAfterTemplateHeadAndMiddle: Rule; - public NoSpaceBeforeTemplateMiddleAndTail: Rule; - public SpaceBeforeTemplateMiddleAndTail: Rule; - - // No space after { and before } in JSX expression - public NoSpaceAfterOpenBraceInJsxExpression: Rule; - public SpaceAfterOpenBraceInJsxExpression: Rule; - public NoSpaceBeforeCloseBraceInJsxExpression: Rule; - public SpaceBeforeCloseBraceInJsxExpression: Rule; - - // JSX opening elements - public SpaceBeforeJsxAttribute: Rule; - public SpaceBeforeSlashInJsxOpeningElement: Rule; - public NoSpaceBeforeGreaterThanTokenInJsxOpeningElement: Rule; - public NoSpaceBeforeEqualInJsxAttribute: Rule; - public NoSpaceAfterEqualInJsxAttribute: Rule; - - // No space after type assertions - public NoSpaceAfterTypeAssertion: Rule; - public SpaceAfterTypeAssertion: Rule; - - // No space before non-null assertion operator - public NoSpaceBeforeNonNullAssertionOperator: Rule; - - constructor() { - /// - /// Common Rules - /// + // Place a space before open brace in a control flow construct + const controlOpenBraceLeftTokenRange = tokenRangeFrom([SyntaxKind.CloseParenToken, SyntaxKind.MultiLineCommentTrivia, SyntaxKind.DoKeyword, SyntaxKind.TryKeyword, SyntaxKind.FinallyKeyword, SyntaxKind.ElseKeyword]); + // These rules are higher in priority than user-configurable + const highPriorityCommonRules = [ // Leave comments alone - this.IgnoreBeforeComment = new Rule(RuleDescriptor.create4(Shared.TokenRange.Any, Shared.TokenRange.Comments), RuleOperation.create1(RuleAction.Ignore)); - this.IgnoreAfterLineComment = new Rule(RuleDescriptor.create3(SyntaxKind.SingleLineCommentTrivia, Shared.TokenRange.Any), RuleOperation.create1(RuleAction.Ignore)); + rule("IgnoreBeforeComment", anyToken, comments, anyContext, RuleAction.Ignore), + rule("IgnoreAfterLineComment", SyntaxKind.SingleLineCommentTrivia, anyToken, anyContext, RuleAction.Ignore), - // Space after keyword but not before ; or : or ? - this.NoSpaceBeforeSemicolon = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.SemicolonToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.NoSpaceBeforeColon = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.ColonToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsNotBinaryOpContext), RuleAction.Delete)); - this.NoSpaceBeforeQuestionMark = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.QuestionToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsNotBinaryOpContext), RuleAction.Delete)); - this.SpaceAfterColon = new Rule(RuleDescriptor.create3(SyntaxKind.ColonToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsNotBinaryOpContext), RuleAction.Space)); - this.SpaceAfterQuestionMarkInConditionalOperator = new Rule(RuleDescriptor.create3(SyntaxKind.QuestionToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsConditionalOperatorContext), RuleAction.Space)); - this.NoSpaceAfterQuestionMark = new Rule(RuleDescriptor.create3(SyntaxKind.QuestionToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.SpaceAfterSemicolon = new Rule(RuleDescriptor.create3(SyntaxKind.SemicolonToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); + rule("NoSpaceBeforeColon", anyToken, SyntaxKind.ColonToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.Delete), + rule("SpaceAfterColon", SyntaxKind.ColonToken, anyToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.Space), + rule("NoSpaceBeforeQuestionMark", anyToken, SyntaxKind.QuestionToken, [isNonJsxSameLineTokenContext, isNotBinaryOpContext], RuleAction.Delete), + // insert space after '?' only when it is used in conditional operator + rule("SpaceAfterQuestionMarkInConditionalOperator", SyntaxKind.QuestionToken, anyToken, [isNonJsxSameLineTokenContext, isConditionalOperatorContext], RuleAction.Space), - // Space after }. - this.SpaceAfterCloseBrace = new Rule(RuleDescriptor.create3(SyntaxKind.CloseBraceToken, Shared.TokenRange.FromRange(SyntaxKind.FirstToken, SyntaxKind.LastToken, [SyntaxKind.CloseParenToken])), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsAfterCodeBlockContext), RuleAction.Space)); + // in other cases there should be no space between '?' and next token + rule("NoSpaceAfterQuestionMark", SyntaxKind.QuestionToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), - // Special case for (}, else) and (}, while) since else & while tokens are not part of the tree which makes SpaceAfterCloseBrace rule not applied - this.SpaceBetweenCloseBraceAndElse = new Rule(RuleDescriptor.create1(SyntaxKind.CloseBraceToken, SyntaxKind.ElseKeyword), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); - this.SpaceBetweenCloseBraceAndWhile = new Rule(RuleDescriptor.create1(SyntaxKind.CloseBraceToken, SyntaxKind.WhileKeyword), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); - this.NoSpaceAfterCloseBrace = new Rule(RuleDescriptor.create3(SyntaxKind.CloseBraceToken, Shared.TokenRange.FromTokens([SyntaxKind.CloseBracketToken, SyntaxKind.CommaToken, SyntaxKind.SemicolonToken])), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - - // No space for dot - this.NoSpaceBeforeDot = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.DotToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.NoSpaceAfterDot = new Rule(RuleDescriptor.create3(SyntaxKind.DotToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - - // No space before and after indexer - this.NoSpaceBeforeOpenBracket = new Rule( - RuleDescriptor.create2(Shared.TokenRange.AnyExcept(SyntaxKind.AsyncKeyword), SyntaxKind.OpenBracketToken), - RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.NoSpaceAfterCloseBracket = new Rule(RuleDescriptor.create3(SyntaxKind.CloseBracketToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsNotBeforeBlockInFunctionDeclarationContext), RuleAction.Delete)); - - // Place a space before open brace in a function declaration - this.FunctionOpenBraceLeftTokenRange = Shared.TokenRange.AnyIncludingMultilineComments; - this.SpaceBeforeOpenBraceInFunction = new Rule(RuleDescriptor.create2(this.FunctionOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForFunctions"), Rules.IsFunctionDeclContext, Rules.IsBeforeBlockContext, Rules.IsNotFormatOnEnter, Rules.IsSameLineTokenOrBeforeBlockContext), RuleAction.Space), RuleFlags.CanDeleteNewLines); - - // Place a space before open brace in a TypeScript declaration that has braces as children (class, module, enum, etc) - this.TypeScriptOpenBraceLeftTokenRange = Shared.TokenRange.FromTokens([SyntaxKind.Identifier, SyntaxKind.MultiLineCommentTrivia, SyntaxKind.ClassKeyword, SyntaxKind.ExportKeyword, SyntaxKind.ImportKeyword]); - this.SpaceBeforeOpenBraceInTypeScriptDeclWithBlock = new Rule(RuleDescriptor.create2(this.TypeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForFunctions"), Rules.IsTypeScriptDeclWithBlockContext, Rules.IsNotFormatOnEnter, Rules.IsSameLineTokenOrBeforeBlockContext), RuleAction.Space), RuleFlags.CanDeleteNewLines); - - // Place a space before open brace in a control flow construct - this.ControlOpenBraceLeftTokenRange = Shared.TokenRange.FromTokens([SyntaxKind.CloseParenToken, SyntaxKind.MultiLineCommentTrivia, SyntaxKind.DoKeyword, SyntaxKind.TryKeyword, SyntaxKind.FinallyKeyword, SyntaxKind.ElseKeyword]); - this.SpaceBeforeOpenBraceInControl = new Rule(RuleDescriptor.create2(this.ControlOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.isOptionDisabledOrUndefinedOrTokensOnSameLine("placeOpenBraceOnNewLineForControlBlocks"), Rules.IsControlDeclContext, Rules.IsNotFormatOnEnter, Rules.IsSameLineTokenOrBeforeBlockContext), RuleAction.Space), RuleFlags.CanDeleteNewLines); - - // Insert a space after { and before } in single-line contexts, but remove space from empty object literals {}. - this.SpaceAfterOpenBrace = new Rule(RuleDescriptor.create3(SyntaxKind.OpenBraceToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), Rules.IsBraceWrappedContext), RuleAction.Space)); - this.SpaceBeforeCloseBrace = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), Rules.IsBraceWrappedContext), RuleAction.Space)); - this.NoSpaceAfterOpenBrace = new Rule(RuleDescriptor.create3(SyntaxKind.OpenBraceToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.NoSpaceBeforeCloseBrace = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.NoSpaceBetweenEmptyBraceBrackets = new Rule(RuleDescriptor.create1(SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsObjectContext), RuleAction.Delete)); - - // Insert new line after { and before } in multi-line contexts. - this.NewLineAfterOpenBraceInBlockContext = new Rule(RuleDescriptor.create3(SyntaxKind.OpenBraceToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsMultilineBlockContext), RuleAction.NewLine)); - - // For functions and control block place } on a new line [multi-line rule] - this.NewLineBeforeCloseBraceInBlockContext = new Rule(RuleDescriptor.create2(Shared.TokenRange.AnyIncludingMultilineComments, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsMultilineBlockContext), RuleAction.NewLine)); + rule("NoSpaceBeforeDot", anyToken, SyntaxKind.DotToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), + rule("NoSpaceAfterDot", SyntaxKind.DotToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), // Special handling of unary operators. // Prefix operators generally shouldn't have a space between // them and their target unary expression. - this.NoSpaceAfterUnaryPrefixOperator = new Rule(RuleDescriptor.create4(Shared.TokenRange.UnaryPrefixOperators, Shared.TokenRange.UnaryPrefixExpressions), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsNotBinaryOpContext), RuleAction.Delete)); - this.NoSpaceAfterUnaryPreincrementOperator = new Rule(RuleDescriptor.create3(SyntaxKind.PlusPlusToken, Shared.TokenRange.UnaryPreincrementExpressions), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.NoSpaceAfterUnaryPredecrementOperator = new Rule(RuleDescriptor.create3(SyntaxKind.MinusMinusToken, Shared.TokenRange.UnaryPredecrementExpressions), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.NoSpaceBeforeUnaryPostincrementOperator = new Rule(RuleDescriptor.create2(Shared.TokenRange.UnaryPostincrementExpressions, SyntaxKind.PlusPlusToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.NoSpaceBeforeUnaryPostdecrementOperator = new Rule(RuleDescriptor.create2(Shared.TokenRange.UnaryPostdecrementExpressions, SyntaxKind.MinusMinusToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); + 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), // 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 - this.SpaceAfterPostincrementWhenFollowedByAdd = new Rule(RuleDescriptor.create1(SyntaxKind.PlusPlusToken, SyntaxKind.PlusToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space)); - this.SpaceAfterAddWhenFollowedByUnaryPlus = new Rule(RuleDescriptor.create1(SyntaxKind.PlusToken, SyntaxKind.PlusToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space)); - this.SpaceAfterAddWhenFollowedByPreincrement = new Rule(RuleDescriptor.create1(SyntaxKind.PlusToken, SyntaxKind.PlusPlusToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space)); - this.SpaceAfterPostdecrementWhenFollowedBySubtract = new Rule(RuleDescriptor.create1(SyntaxKind.MinusMinusToken, SyntaxKind.MinusToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space)); - this.SpaceAfterSubtractWhenFollowedByUnaryMinus = new Rule(RuleDescriptor.create1(SyntaxKind.MinusToken, SyntaxKind.MinusToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space)); - this.SpaceAfterSubtractWhenFollowedByPredecrement = new Rule(RuleDescriptor.create1(SyntaxKind.MinusToken, SyntaxKind.MinusMinusToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space)); + 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), - this.NoSpaceBeforeComma = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CommaToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); + rule("NoSpaceAfterCloseBrace", SyntaxKind.CloseBraceToken, [SyntaxKind.CloseBracketToken, SyntaxKind.CommaToken, SyntaxKind.SemicolonToken], [isNonJsxSameLineTokenContext], RuleAction.Delete), + // For functions and control block place } on a new line [multi-line rule] + rule("NewLineBeforeCloseBraceInBlockContext", anyTokenIncludingMultilineComments, SyntaxKind.CloseBraceToken, [isMultilineBlockContext], RuleAction.NewLine), - this.SpaceAfterCertainKeywords = new Rule(RuleDescriptor.create4(Shared.TokenRange.FromTokens([SyntaxKind.VarKeyword, SyntaxKind.ThrowKeyword, SyntaxKind.NewKeyword, SyntaxKind.DeleteKeyword, SyntaxKind.ReturnKeyword, SyntaxKind.TypeOfKeyword, SyntaxKind.AwaitKeyword]), Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); - this.NoSpaceAfterNewKeywordOnConstructorSignature = new Rule(RuleDescriptor.create1(SyntaxKind.NewKeyword, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsConstructorSignatureContext), RuleAction.Delete)); - this.SpaceAfterLetConstInVariableDeclaration = new Rule(RuleDescriptor.create4(Shared.TokenRange.FromTokens([SyntaxKind.LetKeyword, SyntaxKind.ConstKeyword]), Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsStartOfVariableDeclarationList), RuleAction.Space)); - this.NoSpaceBeforeOpenParenInFuncCall = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsFunctionCallOrNewContext, Rules.IsPreviousTokenNotComma), RuleAction.Delete)); - this.SpaceAfterFunctionInFuncDecl = new Rule(RuleDescriptor.create3(SyntaxKind.FunctionKeyword, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsFunctionDeclContext), RuleAction.Space)); - this.SpaceBeforeOpenParenInFuncDecl = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceBeforeFunctionParenthesis"), Rules.IsNonJsxSameLineTokenContext, Rules.IsFunctionDeclContext), RuleAction.Space)); - this.NoSpaceBeforeOpenParenInFuncDecl = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceBeforeFunctionParenthesis"), Rules.IsNonJsxSameLineTokenContext, Rules.IsFunctionDeclContext), RuleAction.Delete)); - this.SpaceAfterVoidOperator = new Rule(RuleDescriptor.create3(SyntaxKind.VoidKeyword, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsVoidOpContext), RuleAction.Space)); + // Space/new line after }. + rule("SpaceAfterCloseBrace", SyntaxKind.CloseBraceToken, anyTokenExcept(SyntaxKind.CloseParenToken), [isNonJsxSameLineTokenContext, isAfterCodeBlockContext], RuleAction.Space), + // 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), - this.NoSpaceBetweenReturnAndSemicolon = new Rule(RuleDescriptor.create1(SyntaxKind.ReturnKeyword, SyntaxKind.SemicolonToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); + rule("NoSpaceBetweenFunctionKeywordAndStar", SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken, [isFunctionDeclarationOrFunctionExpressionContext], RuleAction.Delete), + rule("SpaceAfterStarInGeneratorDeclaration", SyntaxKind.AsteriskToken, [SyntaxKind.Identifier, SyntaxKind.OpenParenToken], [isFunctionDeclarationOrFunctionExpressionContext], RuleAction.Space), - // 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] - this.SpaceBetweenStatements = new Rule(RuleDescriptor.create4(Shared.TokenRange.FromTokens([SyntaxKind.CloseParenToken, SyntaxKind.DoKeyword, SyntaxKind.ElseKeyword, SyntaxKind.CaseKeyword]), Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsNonJsxElementContext, Rules.IsNotForContext), RuleAction.Space)); - - // This low-pri rule takes care of "try {" and "finally {" in case the rule SpaceBeforeOpenBraceInControl didn't execute on FormatOnEnter. - this.SpaceAfterTryFinally = new Rule(RuleDescriptor.create2(Shared.TokenRange.FromTokens([SyntaxKind.TryKeyword, SyntaxKind.FinallyKeyword]), SyntaxKind.OpenBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); + rule("SpaceAfterFunctionInFuncDecl", SyntaxKind.FunctionKeyword, anyToken, [isFunctionDeclContext], RuleAction.Space), + // Insert new line after { and before } in multi-line contexts. + rule("NewLineAfterOpenBraceInBlockContext", SyntaxKind.OpenBraceToken, anyToken, [isMultilineBlockContext], RuleAction.NewLine), + // 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) {} - this.SpaceAfterGetSetInMember = new Rule(RuleDescriptor.create2(Shared.TokenRange.FromTokens([SyntaxKind.GetKeyword, SyntaxKind.SetKeyword]), SyntaxKind.Identifier), RuleOperation.create2(new RuleOperationContext(Rules.IsFunctionDeclContext), RuleAction.Space)); + rule("SpaceAfterGetSetInMember", [SyntaxKind.GetKeyword, SyntaxKind.SetKeyword], SyntaxKind.Identifier, [isFunctionDeclContext], RuleAction.Space), + + rule("NoSpaceBetweenYieldKeywordAndStar", SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken, [isNonJsxSameLineTokenContext, isYieldOrYieldStarWithOperand], RuleAction.Delete), + rule("SpaceBetweenYieldOrYieldStarAndOperand", [SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken], anyToken, [isNonJsxSameLineTokenContext, isYieldOrYieldStarWithOperand], RuleAction.Space), + + 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), // Special case for binary operators (that are keywords). For these we have to add a space and shouldn't follow any user options. - this.SpaceBeforeBinaryKeywordOperator = new Rule(RuleDescriptor.create4(Shared.TokenRange.Any, Shared.TokenRange.BinaryKeywordOperators), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space)); - this.SpaceAfterBinaryKeywordOperator = new Rule(RuleDescriptor.create4(Shared.TokenRange.BinaryKeywordOperators, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space)); + rule("SpaceBeforeBinaryKeywordOperator", anyToken, binaryKeywordOperators, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Space), + rule("SpaceAfterBinaryKeywordOperator", binaryKeywordOperators, anyToken, [isNonJsxSameLineTokenContext, isBinaryOpContext], RuleAction.Space), - // TypeScript-specific higher priority rules - - this.SpaceAfterConstructor = new Rule(RuleDescriptor.create1(SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceAfterConstructor"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); - this.NoSpaceAfterConstructor = new Rule(RuleDescriptor.create1(SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceAfterConstructor"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - - // Use of module as a function call. e.g.: import m2 = module("m2"); - this.NoSpaceAfterModuleImport = new Rule(RuleDescriptor.create2(Shared.TokenRange.FromTokens([SyntaxKind.ModuleKeyword, SyntaxKind.RequireKeyword]), SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - - // Add a space around certain TypeScript keywords - this.SpaceAfterCertainTypeScriptKeywords = new Rule(RuleDescriptor.create4(Shared.TokenRange.FromTokens([SyntaxKind.AbstractKeyword, SyntaxKind.ClassKeyword, SyntaxKind.DeclareKeyword, SyntaxKind.DefaultKeyword, SyntaxKind.EnumKeyword, SyntaxKind.ExportKeyword, SyntaxKind.ExtendsKeyword, SyntaxKind.GetKeyword, SyntaxKind.ImplementsKeyword, SyntaxKind.ImportKeyword, SyntaxKind.InterfaceKeyword, SyntaxKind.ModuleKeyword, SyntaxKind.NamespaceKeyword, SyntaxKind.PrivateKeyword, SyntaxKind.PublicKeyword, SyntaxKind.ProtectedKeyword, SyntaxKind.ReadonlyKeyword, SyntaxKind.SetKeyword, SyntaxKind.StaticKeyword, SyntaxKind.TypeKeyword, SyntaxKind.FromKeyword, SyntaxKind.KeyOfKeyword]), Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); - this.SpaceBeforeCertainTypeScriptKeywords = new Rule(RuleDescriptor.create4(Shared.TokenRange.Any, Shared.TokenRange.FromTokens([SyntaxKind.ExtendsKeyword, SyntaxKind.ImplementsKeyword, SyntaxKind.FromKeyword])), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); - - // Treat string literals in module names as identifiers, and add a space between the literal and the opening Brace braces, e.g.: module "m2" { - this.SpaceAfterModuleName = new Rule(RuleDescriptor.create1(SyntaxKind.StringLiteral, SyntaxKind.OpenBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsModuleDeclContext), RuleAction.Space)); - - // Lambda expressions - this.SpaceBeforeArrow = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.EqualsGreaterThanToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); - this.SpaceAfterArrow = new Rule(RuleDescriptor.create3(SyntaxKind.EqualsGreaterThanToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); - - // Optional parameters and let args - this.NoSpaceAfterEllipsis = new Rule(RuleDescriptor.create1(SyntaxKind.DotDotDotToken, SyntaxKind.Identifier), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.NoSpaceAfterOptionalParameters = new Rule(RuleDescriptor.create3(SyntaxKind.QuestionToken, Shared.TokenRange.FromTokens([SyntaxKind.CloseParenToken, SyntaxKind.CommaToken])), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsNotBinaryOpContext), RuleAction.Delete)); - - // generics and type assertions - this.NoSpaceBeforeOpenAngularBracket = new Rule(RuleDescriptor.create2(Shared.TokenRange.TypeNames, SyntaxKind.LessThanToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsTypeArgumentOrParameterOrAssertionContext), RuleAction.Delete)); - this.NoSpaceBetweenCloseParenAndAngularBracket = new Rule(RuleDescriptor.create1(SyntaxKind.CloseParenToken, SyntaxKind.LessThanToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsTypeArgumentOrParameterOrAssertionContext), RuleAction.Delete)); - this.NoSpaceAfterOpenAngularBracket = new Rule(RuleDescriptor.create3(SyntaxKind.LessThanToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsTypeArgumentOrParameterOrAssertionContext), RuleAction.Delete)); - this.NoSpaceBeforeCloseAngularBracket = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.GreaterThanToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsTypeArgumentOrParameterOrAssertionContext), RuleAction.Delete)); - this.NoSpaceAfterCloseAngularBracket = new Rule(RuleDescriptor.create3(SyntaxKind.GreaterThanToken, Shared.TokenRange.FromTokens([SyntaxKind.OpenParenToken, SyntaxKind.OpenBracketToken, SyntaxKind.GreaterThanToken, SyntaxKind.CommaToken])), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsTypeArgumentOrParameterOrAssertionContext), RuleAction.Delete)); - - // Remove spaces in empty interface literals. e.g.: x: {} - this.NoSpaceBetweenEmptyInterfaceBraceBrackets = new Rule(RuleDescriptor.create1(SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsObjectTypeContext), RuleAction.Delete)); - - // decorators - this.SpaceBeforeAt = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.AtToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); - this.NoSpaceAfterAt = new Rule(RuleDescriptor.create3(SyntaxKind.AtToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.SpaceAfterDecorator = new Rule(RuleDescriptor.create4(Shared.TokenRange.Any, Shared.TokenRange.FromTokens([SyntaxKind.AbstractKeyword, SyntaxKind.Identifier, SyntaxKind.ExportKeyword, SyntaxKind.DefaultKeyword, SyntaxKind.ClassKeyword, SyntaxKind.StaticKeyword, SyntaxKind.PublicKeyword, SyntaxKind.PrivateKeyword, SyntaxKind.ProtectedKeyword, SyntaxKind.GetKeyword, SyntaxKind.SetKeyword, SyntaxKind.OpenBracketToken, SyntaxKind.AsteriskToken])), RuleOperation.create2(new RuleOperationContext(Rules.IsEndOfDecoratorContextOnSameLine), RuleAction.Space)); - - this.NoSpaceBetweenFunctionKeywordAndStar = new Rule(RuleDescriptor.create1(SyntaxKind.FunctionKeyword, SyntaxKind.AsteriskToken), RuleOperation.create2(new RuleOperationContext(Rules.IsFunctionDeclarationOrFunctionExpressionContext), RuleAction.Delete)); - this.SpaceAfterStarInGeneratorDeclaration = new Rule(RuleDescriptor.create3(SyntaxKind.AsteriskToken, Shared.TokenRange.FromTokens([SyntaxKind.Identifier, SyntaxKind.OpenParenToken])), RuleOperation.create2(new RuleOperationContext(Rules.IsFunctionDeclarationOrFunctionExpressionContext), RuleAction.Space)); - this.NoSpaceBetweenYieldKeywordAndStar = new Rule(RuleDescriptor.create1(SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsYieldOrYieldStarWithOperand), RuleAction.Delete)); - this.SpaceBetweenYieldOrYieldStarAndOperand = new Rule(RuleDescriptor.create4(Shared.TokenRange.FromTokens([SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken]), Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsYieldOrYieldStarWithOperand), RuleAction.Space)); + rule("SpaceAfterVoidOperator", SyntaxKind.VoidKeyword, anyToken, [isNonJsxSameLineTokenContext, isVoidOpContext], RuleAction.Space), // Async-await - this.SpaceBetweenAsyncAndOpenParen = new Rule(RuleDescriptor.create1(SyntaxKind.AsyncKeyword, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsArrowFunctionContext, Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); - this.SpaceBetweenAsyncAndFunctionKeyword = new Rule(RuleDescriptor.create1(SyntaxKind.AsyncKeyword, SyntaxKind.FunctionKeyword), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); + rule("SpaceBetweenAsyncAndOpenParen", SyntaxKind.AsyncKeyword, SyntaxKind.OpenParenToken, [isArrowFunctionContext, isNonJsxSameLineTokenContext], RuleAction.Space), + rule("SpaceBetweenAsyncAndFunctionKeyword", SyntaxKind.AsyncKeyword, SyntaxKind.FunctionKeyword, [isNonJsxSameLineTokenContext], RuleAction.Space), // template string - this.NoSpaceBetweenTagAndTemplateString = new Rule(RuleDescriptor.create3(SyntaxKind.Identifier, Shared.TokenRange.FromTokens([SyntaxKind.NoSubstitutionTemplateLiteral, SyntaxKind.TemplateHead])), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); + rule("NoSpaceBetweenTagAndTemplateString", SyntaxKind.Identifier, [SyntaxKind.NoSubstitutionTemplateLiteral, SyntaxKind.TemplateHead], [isNonJsxSameLineTokenContext], RuleAction.Delete), - // jsx opening element - this.SpaceBeforeJsxAttribute = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.Identifier), RuleOperation.create2(new RuleOperationContext(Rules.IsNextTokenParentJsxAttribute, Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); - this.SpaceBeforeSlashInJsxOpeningElement = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.SlashToken), RuleOperation.create2(new RuleOperationContext(Rules.IsJsxSelfClosingElementContext, Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); - this.NoSpaceBeforeGreaterThanTokenInJsxOpeningElement = new Rule(RuleDescriptor.create1(SyntaxKind.SlashToken, SyntaxKind.GreaterThanToken), RuleOperation.create2(new RuleOperationContext(Rules.IsJsxSelfClosingElementContext, Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.NoSpaceBeforeEqualInJsxAttribute = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.EqualsToken), RuleOperation.create2(new RuleOperationContext(Rules.IsJsxAttributeContext, Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.NoSpaceAfterEqualInJsxAttribute = new Rule(RuleDescriptor.create3(SyntaxKind.EqualsToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsJsxAttributeContext, Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); + // 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), - // No space before non-null assertion operator - this.NoSpaceBeforeNonNullAssertionOperator = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.ExclamationToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsNonNullAssertionContext), RuleAction.Delete)); + // 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), + // Add a space around certain TypeScript keywords + rule( + "SpaceAfterCertainTypeScriptKeywords", + [ + SyntaxKind.AbstractKeyword, + SyntaxKind.ClassKeyword, + SyntaxKind.DeclareKeyword, + SyntaxKind.DefaultKeyword, + SyntaxKind.EnumKeyword, + SyntaxKind.ExportKeyword, + SyntaxKind.ExtendsKeyword, + SyntaxKind.GetKeyword, + SyntaxKind.ImplementsKeyword, + SyntaxKind.ImportKeyword, + SyntaxKind.InterfaceKeyword, + SyntaxKind.ModuleKeyword, + SyntaxKind.NamespaceKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.ReadonlyKeyword, + SyntaxKind.SetKeyword, + SyntaxKind.StaticKeyword, + SyntaxKind.TypeKeyword, + SyntaxKind.FromKeyword, + SyntaxKind.KeyOfKeyword, + ], + anyToken, + [isNonJsxSameLineTokenContext], + RuleAction.Space), + rule( + "SpaceBeforeCertainTypeScriptKeywords", + anyToken, + [SyntaxKind.ExtendsKeyword, SyntaxKind.ImplementsKeyword, SyntaxKind.FromKeyword], + [isNonJsxSameLineTokenContext], + RuleAction.Space), + // Treat string literals in module names as identifiers, and add a space between the literal and the opening Brace braces, e.g.: module "m2" { + rule("SpaceAfterModuleName", SyntaxKind.StringLiteral, SyntaxKind.OpenBraceToken, [isModuleDeclContext], RuleAction.Space), - /// - /// Rules controlled by user options - /// + // Lambda expressions + rule("SpaceBeforeArrow", anyToken, SyntaxKind.EqualsGreaterThanToken, [isNonJsxSameLineTokenContext], RuleAction.Space), + rule("SpaceAfterArrow", SyntaxKind.EqualsGreaterThanToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.Space), - // Insert space after comma delimiter - this.SpaceAfterComma = new Rule(RuleDescriptor.create3(SyntaxKind.CommaToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceAfterCommaDelimiter"), Rules.IsNonJsxSameLineTokenContext, Rules.IsNonJsxElementContext, Rules.IsNextTokenNotCloseBracket), RuleAction.Space)); - this.NoSpaceAfterComma = new Rule(RuleDescriptor.create3(SyntaxKind.CommaToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceAfterCommaDelimiter"), Rules.IsNonJsxSameLineTokenContext, Rules.IsNonJsxElementContext), RuleAction.Delete)); + // 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), - // Insert space before and after binary operators - this.SpaceBeforeBinaryOperator = new Rule(RuleDescriptor.create4(Shared.TokenRange.Any, Shared.TokenRange.BinaryOperators), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceBeforeAndAfterBinaryOperators"), Rules.IsNonJsxSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space)); - this.SpaceAfterBinaryOperator = new Rule(RuleDescriptor.create4(Shared.TokenRange.BinaryOperators, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceBeforeAndAfterBinaryOperators"), Rules.IsNonJsxSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space)); - this.NoSpaceBeforeBinaryOperator = new Rule(RuleDescriptor.create4(Shared.TokenRange.Any, Shared.TokenRange.BinaryOperators), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceBeforeAndAfterBinaryOperators"), Rules.IsNonJsxSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Delete)); - this.NoSpaceAfterBinaryOperator = new Rule(RuleDescriptor.create4(Shared.TokenRange.BinaryOperators, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceBeforeAndAfterBinaryOperators"), Rules.IsNonJsxSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Delete)); + // Remove spaces in empty interface literals. e.g.: x: {} + rule("NoSpaceBetweenEmptyInterfaceBraceBrackets", SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, [isNonJsxSameLineTokenContext, isObjectTypeContext], RuleAction.Delete), + + // 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("NoSpaceAfterCloseAngularBracket", + SyntaxKind.GreaterThanToken, + [SyntaxKind.OpenParenToken, SyntaxKind.OpenBracketToken, SyntaxKind.GreaterThanToken, SyntaxKind.CommaToken], + [isNonJsxSameLineTokenContext, isTypeArgumentOrParameterOrAssertionContext], + RuleAction.Delete), + + // decorators + rule("SpaceBeforeAt", anyToken, SyntaxKind.AtToken, [isNonJsxSameLineTokenContext], RuleAction.Space), + rule("NoSpaceAfterAt", SyntaxKind.AtToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), + // Insert space after @ in decorator + rule("SpaceAfterDecorator", + anyToken, + [ + SyntaxKind.AbstractKeyword, + SyntaxKind.Identifier, + SyntaxKind.ExportKeyword, + SyntaxKind.DefaultKeyword, + SyntaxKind.ClassKeyword, + SyntaxKind.StaticKeyword, + SyntaxKind.PublicKeyword, + SyntaxKind.PrivateKeyword, + SyntaxKind.ProtectedKeyword, + SyntaxKind.GetKeyword, + SyntaxKind.SetKeyword, + SyntaxKind.OpenBracketToken, + SyntaxKind.AsteriskToken, + ], + [isEndOfDecoratorContextOnSameLine], + RuleAction.Space), + + rule("NoSpaceBeforeNonNullAssertionOperator", anyToken, SyntaxKind.ExclamationToken, [isNonJsxSameLineTokenContext, isNonNullAssertionContext], RuleAction.Delete), + rule("NoSpaceAfterNewKeywordOnConstructorSignature", SyntaxKind.NewKeyword, SyntaxKind.OpenParenToken, [isNonJsxSameLineTokenContext, isConstructorSignatureContext], RuleAction.Delete), + ]; + + // 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("SpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionEnabled("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementContext, isNextTokenNotCloseBracket], RuleAction.Space), + rule("NoSpaceAfterComma", SyntaxKind.CommaToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterCommaDelimiter"), isNonJsxSameLineTokenContext, isNonJsxElementContext], RuleAction.Delete), + + // Insert space after function keyword for anonymous functions + rule("SpaceAfterAnonymousFunctionKeyword", SyntaxKind.FunctionKeyword, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), isFunctionDeclContext], RuleAction.Space), + rule("NoSpaceAfterAnonymousFunctionKeyword", SyntaxKind.FunctionKeyword, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), isFunctionDeclContext], RuleAction.Delete), // Insert space after keywords in control flow statements - this.SpaceAfterKeywordInControl = new Rule(RuleDescriptor.create2(Shared.TokenRange.Keywords, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceAfterKeywordsInControlFlowStatements"), Rules.IsControlDeclContext), RuleAction.Space)); - this.NoSpaceAfterKeywordInControl = new Rule(RuleDescriptor.create2(Shared.TokenRange.Keywords, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceAfterKeywordsInControlFlowStatements"), Rules.IsControlDeclContext), RuleAction.Delete)); + rule("SpaceAfterKeywordInControl", keywords, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceAfterKeywordsInControlFlowStatements"), isControlDeclContext], RuleAction.Space), + rule("NoSpaceAfterKeywordInControl", keywords, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceAfterKeywordsInControlFlowStatements"), isControlDeclContext], RuleAction.Delete), + + // 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), + + // 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), + + // 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), + + // 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), + + // 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), + + // 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), + + // 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("SpaceBeforeOpenParenInFuncDecl", anyToken, SyntaxKind.OpenParenToken, [isOptionEnabled("insertSpaceBeforeFunctionParenthesis"), isNonJsxSameLineTokenContext, isFunctionDeclContext], RuleAction.Space), + rule("NoSpaceBeforeOpenParenInFuncDecl", anyToken, SyntaxKind.OpenParenToken, [isOptionDisabledOrUndefined("insertSpaceBeforeFunctionParenthesis"), isNonJsxSameLineTokenContext, isFunctionDeclContext], RuleAction.Delete), + + // Open Brace braces after control block + rule("NewLineBeforeOpenBraceInControl", controlOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForControlBlocks"), isControlDeclContext, isBeforeMultilineBlockContext], RuleAction.NewLine, RuleFlags.CanDeleteNewLines), // Open Brace braces after function // TypeScript: Function can have return types, which can be made of tons of different token kinds - this.NewLineBeforeOpenBraceInFunction = new Rule(RuleDescriptor.create2(this.FunctionOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("placeOpenBraceOnNewLineForFunctions"), Rules.IsFunctionDeclContext, Rules.IsBeforeMultilineBlockContext), RuleAction.NewLine), RuleFlags.CanDeleteNewLines); - + rule("NewLineBeforeOpenBraceInFunction", functionOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForFunctions"), isFunctionDeclContext, isBeforeMultilineBlockContext], RuleAction.NewLine, RuleFlags.CanDeleteNewLines), // Open Brace braces after TypeScript module/class/interface - this.NewLineBeforeOpenBraceInTypeScriptDeclWithBlock = new Rule(RuleDescriptor.create2(this.TypeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("placeOpenBraceOnNewLineForFunctions"), Rules.IsTypeScriptDeclWithBlockContext, Rules.IsBeforeMultilineBlockContext), RuleAction.NewLine), RuleFlags.CanDeleteNewLines); + rule("NewLineBeforeOpenBraceInTypeScriptDeclWithBlock", typeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken, [isOptionEnabled("placeOpenBraceOnNewLineForFunctions"), isTypeScriptDeclWithBlockContext, isBeforeMultilineBlockContext], RuleAction.NewLine, RuleFlags.CanDeleteNewLines), - // Open Brace braces after control block - this.NewLineBeforeOpenBraceInControl = new Rule(RuleDescriptor.create2(this.ControlOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("placeOpenBraceOnNewLineForControlBlocks"), Rules.IsControlDeclContext, Rules.IsBeforeMultilineBlockContext), RuleAction.NewLine), RuleFlags.CanDeleteNewLines); + rule("SpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionEnabled("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.Space), + rule("NoSpaceAfterTypeAssertion", SyntaxKind.GreaterThanToken, anyToken, [isOptionDisabledOrUndefined("insertSpaceAfterTypeAssertion"), isNonJsxSameLineTokenContext, isTypeAssertionContext], RuleAction.Delete), + ]; - // Insert space after semicolon in for statement - this.SpaceAfterSemicolonInFor = new Rule(RuleDescriptor.create3(SyntaxKind.SemicolonToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceAfterSemicolonInForStatements"), Rules.IsNonJsxSameLineTokenContext, Rules.IsForContext), RuleAction.Space)); - this.NoSpaceAfterSemicolonInFor = new Rule(RuleDescriptor.create3(SyntaxKind.SemicolonToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceAfterSemicolonInForStatements"), Rules.IsNonJsxSameLineTokenContext, Rules.IsForContext), RuleAction.Delete)); + // These rules are lower in priority than user-configurable + const lowPriorityCommonRules = [ + // Space after keyword but not before ; or : or ? + rule("NoSpaceBeforeSemicolon", anyToken, SyntaxKind.SemicolonToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), - // Insert space after opening and before closing nonempty parenthesis - this.SpaceAfterOpenParen = new Rule(RuleDescriptor.create3(SyntaxKind.OpenParenToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); - this.SpaceBeforeCloseParen = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); - this.SpaceBetweenOpenParens = new Rule(RuleDescriptor.create1(SyntaxKind.OpenParenToken, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); - this.NoSpaceBetweenParens = new Rule(RuleDescriptor.create1(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.NoSpaceAfterOpenParen = new Rule(RuleDescriptor.create3(SyntaxKind.OpenParenToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.NoSpaceBeforeCloseParen = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); + 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), - // Insert space after opening and before closing nonempty brackets - this.SpaceAfterOpenBracket = new Rule(RuleDescriptor.create3(SyntaxKind.OpenBracketToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); - this.SpaceBeforeCloseBracket = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseBracketToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); - this.NoSpaceBetweenBrackets = new Rule(RuleDescriptor.create1(SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.NoSpaceAfterOpenBracket = new Rule(RuleDescriptor.create3(SyntaxKind.OpenBracketToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.NoSpaceBeforeCloseBracket = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseBracketToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); + rule("NoSpaceBeforeComma", anyToken, SyntaxKind.CommaToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), + // No space before and after indexer + rule("NoSpaceBeforeOpenBracket", anyTokenExcept(SyntaxKind.AsyncKeyword), SyntaxKind.OpenBracketToken, [isNonJsxSameLineTokenContext], RuleAction.Delete), + rule("NoSpaceAfterCloseBracket", SyntaxKind.CloseBracketToken, anyToken, [isNonJsxSameLineTokenContext, isNotBeforeBlockInFunctionDeclarationContext], RuleAction.Delete), + rule("SpaceAfterSemicolon", SyntaxKind.SemicolonToken, anyToken, [isNonJsxSameLineTokenContext], RuleAction.Space), - // Insert space after opening and before closing template string braces - this.NoSpaceAfterTemplateHeadAndMiddle = new Rule(RuleDescriptor.create4(Shared.TokenRange.FromTokens([SyntaxKind.TemplateHead, SyntaxKind.TemplateMiddle]), Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.SpaceAfterTemplateHeadAndMiddle = new Rule(RuleDescriptor.create4(Shared.TokenRange.FromTokens([SyntaxKind.TemplateHead, SyntaxKind.TemplateMiddle]), Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); - this.NoSpaceBeforeTemplateMiddleAndTail = new Rule(RuleDescriptor.create4(Shared.TokenRange.Any, Shared.TokenRange.FromTokens([SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail])), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.SpaceBeforeTemplateMiddleAndTail = new Rule(RuleDescriptor.create4(Shared.TokenRange.Any, Shared.TokenRange.FromTokens([SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail])), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces"), Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); + // 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] + rule( + "SpaceBetweenStatements", + [SyntaxKind.CloseParenToken, SyntaxKind.DoKeyword, SyntaxKind.ElseKeyword, SyntaxKind.CaseKeyword], + anyToken, + [isNonJsxSameLineTokenContext, isNonJsxElementContext, isNotForContext], + RuleAction.Space), + // 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), + ]; - // No space after { and before } in JSX expression - this.NoSpaceAfterOpenBraceInJsxExpression = new Rule(RuleDescriptor.create3(SyntaxKind.OpenBraceToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), Rules.IsNonJsxSameLineTokenContext, Rules.IsJsxExpressionContext), RuleAction.Delete)); - this.SpaceAfterOpenBraceInJsxExpression = new Rule(RuleDescriptor.create3(SyntaxKind.OpenBraceToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), Rules.IsNonJsxSameLineTokenContext, Rules.IsJsxExpressionContext), RuleAction.Space)); - this.NoSpaceBeforeCloseBraceInJsxExpression = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), Rules.IsNonJsxSameLineTokenContext, Rules.IsJsxExpressionContext), RuleAction.Delete)); - this.SpaceBeforeCloseBraceInJsxExpression = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces"), Rules.IsNonJsxSameLineTokenContext, Rules.IsJsxExpressionContext), RuleAction.Space)); + return [ + ...highPriorityCommonRules, + ...userConfigurableRules, + ...lowPriorityCommonRules, + ]; + } - // Insert space after function keyword for anonymous functions - this.SpaceAfterAnonymousFunctionKeyword = new Rule(RuleDescriptor.create1(SyntaxKind.FunctionKeyword, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), Rules.IsFunctionDeclContext), RuleAction.Space)); - this.NoSpaceAfterAnonymousFunctionKeyword = new Rule(RuleDescriptor.create1(SyntaxKind.FunctionKeyword, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceAfterFunctionKeywordForAnonymousFunctions"), Rules.IsFunctionDeclContext), RuleAction.Delete)); + function rule( + debugName: string, + left: SyntaxKind | ReadonlyArray | TokenRange, + right: SyntaxKind | ReadonlyArray | TokenRange, + context: ReadonlyArray, + action: RuleAction, + flags: RuleFlags = RuleFlags.None, + ): RuleSpec { + return { leftTokenRange: toTokenRange(left), rightTokenRange: toTokenRange(right), rule: { debugName, context, action, flags } }; + } - // No space after type assertion - this.NoSpaceAfterTypeAssertion = new Rule(RuleDescriptor.create3(SyntaxKind.GreaterThanToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionDisabledOrUndefined("insertSpaceAfterTypeAssertion"), Rules.IsNonJsxSameLineTokenContext, Rules.IsTypeAssertionContext), RuleAction.Delete)); - this.SpaceAfterTypeAssertion = new Rule(RuleDescriptor.create3(SyntaxKind.GreaterThanToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsOptionEnabled("insertSpaceAfterTypeAssertion"), Rules.IsNonJsxSameLineTokenContext, Rules.IsTypeAssertionContext), RuleAction.Space)); + function tokenRangeFrom(tokens: ReadonlyArray): TokenRange { + return { tokens, isSpecific: true }; + } - // These rules are higher in priority than user-configurable rules. - this.HighPriorityCommonRules = [ - this.IgnoreBeforeComment, this.IgnoreAfterLineComment, - this.NoSpaceBeforeColon, this.SpaceAfterColon, this.NoSpaceBeforeQuestionMark, this.SpaceAfterQuestionMarkInConditionalOperator, - this.NoSpaceAfterQuestionMark, - this.NoSpaceBeforeDot, this.NoSpaceAfterDot, - this.NoSpaceAfterUnaryPrefixOperator, - this.NoSpaceAfterUnaryPreincrementOperator, this.NoSpaceAfterUnaryPredecrementOperator, - this.NoSpaceBeforeUnaryPostincrementOperator, this.NoSpaceBeforeUnaryPostdecrementOperator, - this.SpaceAfterPostincrementWhenFollowedByAdd, - this.SpaceAfterAddWhenFollowedByUnaryPlus, this.SpaceAfterAddWhenFollowedByPreincrement, - this.SpaceAfterPostdecrementWhenFollowedBySubtract, - this.SpaceAfterSubtractWhenFollowedByUnaryMinus, this.SpaceAfterSubtractWhenFollowedByPredecrement, - this.NoSpaceAfterCloseBrace, - this.NewLineBeforeCloseBraceInBlockContext, - this.SpaceAfterCloseBrace, this.SpaceBetweenCloseBraceAndElse, this.SpaceBetweenCloseBraceAndWhile, this.NoSpaceBetweenEmptyBraceBrackets, - this.NoSpaceBetweenFunctionKeywordAndStar, this.SpaceAfterStarInGeneratorDeclaration, - this.SpaceAfterFunctionInFuncDecl, this.NewLineAfterOpenBraceInBlockContext, this.SpaceAfterGetSetInMember, - this.NoSpaceBetweenYieldKeywordAndStar, this.SpaceBetweenYieldOrYieldStarAndOperand, - this.NoSpaceBetweenReturnAndSemicolon, - this.SpaceAfterCertainKeywords, - this.SpaceAfterLetConstInVariableDeclaration, - this.NoSpaceBeforeOpenParenInFuncCall, - this.SpaceBeforeBinaryKeywordOperator, this.SpaceAfterBinaryKeywordOperator, - this.SpaceAfterVoidOperator, - this.SpaceBetweenAsyncAndOpenParen, this.SpaceBetweenAsyncAndFunctionKeyword, - this.NoSpaceBetweenTagAndTemplateString, - this.SpaceBeforeJsxAttribute, this.SpaceBeforeSlashInJsxOpeningElement, this.NoSpaceBeforeGreaterThanTokenInJsxOpeningElement, - this.NoSpaceBeforeEqualInJsxAttribute, this.NoSpaceAfterEqualInJsxAttribute, + function toTokenRange(arg: SyntaxKind | ReadonlyArray | TokenRange): TokenRange { + return typeof arg === "number" ? tokenRangeFrom([arg]) : isArray(arg) ? tokenRangeFrom(arg) : arg; + } - // TypeScript-specific rules - this.NoSpaceAfterModuleImport, - this.SpaceAfterCertainTypeScriptKeywords, this.SpaceBeforeCertainTypeScriptKeywords, - this.SpaceAfterModuleName, - this.SpaceBeforeArrow, this.SpaceAfterArrow, - this.NoSpaceAfterEllipsis, - this.NoSpaceAfterOptionalParameters, - this.NoSpaceBetweenEmptyInterfaceBraceBrackets, - this.NoSpaceBeforeOpenAngularBracket, - this.NoSpaceBetweenCloseParenAndAngularBracket, - this.NoSpaceAfterOpenAngularBracket, - this.NoSpaceBeforeCloseAngularBracket, - this.NoSpaceAfterCloseAngularBracket, - this.SpaceBeforeAt, - this.NoSpaceAfterAt, - this.SpaceAfterDecorator, - this.NoSpaceBeforeNonNullAssertionOperator, - this.NoSpaceAfterNewKeywordOnConstructorSignature - ]; - - // These rules are applied after high priority rules. - this.UserConfigurableRules = [ - this.SpaceAfterConstructor, this.NoSpaceAfterConstructor, - this.SpaceAfterComma, this.NoSpaceAfterComma, - this.SpaceAfterAnonymousFunctionKeyword, this.NoSpaceAfterAnonymousFunctionKeyword, - this.SpaceAfterKeywordInControl, this.NoSpaceAfterKeywordInControl, - this.SpaceAfterOpenParen, this.SpaceBeforeCloseParen, this.SpaceBetweenOpenParens, this.NoSpaceBetweenParens, this.NoSpaceAfterOpenParen, this.NoSpaceBeforeCloseParen, - this.SpaceAfterOpenBracket, this.SpaceBeforeCloseBracket, this.NoSpaceBetweenBrackets, this.NoSpaceAfterOpenBracket, this.NoSpaceBeforeCloseBracket, - this.SpaceAfterOpenBrace, this.SpaceBeforeCloseBrace, this.NoSpaceBetweenEmptyBraceBrackets, this.NoSpaceAfterOpenBrace, this.NoSpaceBeforeCloseBrace, - this.SpaceAfterTemplateHeadAndMiddle, this.SpaceBeforeTemplateMiddleAndTail, this.NoSpaceAfterTemplateHeadAndMiddle, this.NoSpaceBeforeTemplateMiddleAndTail, - this.SpaceAfterOpenBraceInJsxExpression, this.SpaceBeforeCloseBraceInJsxExpression, this.NoSpaceAfterOpenBraceInJsxExpression, this.NoSpaceBeforeCloseBraceInJsxExpression, - this.SpaceAfterSemicolonInFor, this.NoSpaceAfterSemicolonInFor, - this.SpaceBeforeBinaryOperator, this.SpaceAfterBinaryOperator, this.NoSpaceBeforeBinaryOperator, this.NoSpaceAfterBinaryOperator, - this.SpaceBeforeOpenParenInFuncDecl, this.NoSpaceBeforeOpenParenInFuncDecl, - this.NewLineBeforeOpenBraceInControl, - this.NewLineBeforeOpenBraceInFunction, this.NewLineBeforeOpenBraceInTypeScriptDeclWithBlock, - this.SpaceAfterTypeAssertion, this.NoSpaceAfterTypeAssertion - ]; - - // These rules are lower in priority than user-configurable rules. - this.LowPriorityCommonRules = [ - this.NoSpaceBeforeSemicolon, - this.SpaceBeforeOpenBraceInControl, this.SpaceBeforeOpenBraceInFunction, this.SpaceBeforeOpenBraceInTypeScriptDeclWithBlock, - this.NoSpaceBeforeComma, - this.NoSpaceBeforeOpenBracket, - this.NoSpaceAfterCloseBracket, - this.SpaceAfterSemicolon, - this.SpaceBetweenStatements, this.SpaceAfterTryFinally - ]; - - if (Debug.isDebugging) { - const o: ts.MapLike = this; - for (const name in o) { - const rule = o[name]; - if (rule instanceof Rule) { - rule.debugName = name; - } - } + function tokenRangeFromRange(from: SyntaxKind, to: SyntaxKind, except: ReadonlyArray = []): TokenRange { + const tokens: SyntaxKind[] = []; + for (let token = from; token <= to; token++) { + if (!contains(except, token)) { + tokens.push(token); } } + return tokenRangeFrom(tokens); + } - /// - /// Contexts - /// + /// + /// Contexts + /// - static IsOptionEnabled(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { - return (context) => context.options && context.options.hasOwnProperty(optionName) && !!context.options[optionName]; - } + function isOptionEnabled(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { + return (context) => context.options && context.options.hasOwnProperty(optionName) && !!context.options[optionName]; + } - static IsOptionDisabled(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { - return (context) => context.options && context.options.hasOwnProperty(optionName) && !context.options[optionName]; - } + function isOptionDisabled(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { + return (context) => context.options && context.options.hasOwnProperty(optionName) && !context.options[optionName]; + } - static IsOptionDisabledOrUndefined(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { - return (context) => !context.options || !context.options.hasOwnProperty(optionName) || !context.options[optionName]; - } + function isOptionDisabledOrUndefined(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { + return (context) => !context.options || !context.options.hasOwnProperty(optionName) || !context.options[optionName]; + } - static isOptionDisabledOrUndefinedOrTokensOnSameLine(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { - return (context) => !context.options || !context.options.hasOwnProperty(optionName) || !context.options[optionName] || context.TokensAreOnSameLine(); - } + function isOptionDisabledOrUndefinedOrTokensOnSameLine(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { + return (context) => !context.options || !context.options.hasOwnProperty(optionName) || !context.options[optionName] || context.TokensAreOnSameLine(); + } - static IsOptionEnabledOrUndefined(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { - return (context) => !context.options || !context.options.hasOwnProperty(optionName) || !!context.options[optionName]; - } + function isOptionEnabledOrUndefined(optionName: keyof FormatCodeSettings): (context: FormattingContext) => boolean { + return (context) => !context.options || !context.options.hasOwnProperty(optionName) || !!context.options[optionName]; + } - static IsForContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ForStatement; - } + function isForContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ForStatement; + } - static IsNotForContext(context: FormattingContext): boolean { - return !Rules.IsForContext(context); - } + function isNotForContext(context: FormattingContext): boolean { + return !isForContext(context); + } - static IsBinaryOpContext(context: FormattingContext): boolean { + function isBinaryOpContext(context: FormattingContext): boolean { - switch (context.contextNode.kind) { - case SyntaxKind.BinaryExpression: - case SyntaxKind.ConditionalExpression: - case SyntaxKind.AsExpression: - case SyntaxKind.ExportSpecifier: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.TypePredicate: - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - return true; - - // equals in binding elements: function foo([[x, y] = [1, 2]]) - case SyntaxKind.BindingElement: - // equals in type X = ... - case SyntaxKind.TypeAliasDeclaration: - // equal in import a = module('a'); - case SyntaxKind.ImportEqualsDeclaration: - // equal in let a = 0; - case SyntaxKind.VariableDeclaration: - // equal in p = 0; - case SyntaxKind.Parameter: - case SyntaxKind.EnumMember: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - return context.currentTokenSpan.kind === SyntaxKind.EqualsToken || context.nextTokenSpan.kind === SyntaxKind.EqualsToken; - // "in" keyword in for (let x in []) { } - case SyntaxKind.ForInStatement: - // "in" keyword in [P in keyof T]: T[P] - case SyntaxKind.TypeParameter: - return context.currentTokenSpan.kind === SyntaxKind.InKeyword || context.nextTokenSpan.kind === SyntaxKind.InKeyword; - // Technically, "of" is not a binary operator, but format it the same way as "in" - case SyntaxKind.ForOfStatement: - return context.currentTokenSpan.kind === SyntaxKind.OfKeyword || context.nextTokenSpan.kind === SyntaxKind.OfKeyword; - } - return false; - } - - static IsNotBinaryOpContext(context: FormattingContext): boolean { - return !Rules.IsBinaryOpContext(context); - } - - static IsConditionalOperatorContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ConditionalExpression; - } - - static IsSameLineTokenOrBeforeBlockContext(context: FormattingContext): boolean { - return context.TokensAreOnSameLine() || Rules.IsBeforeBlockContext(context); - } - - static IsBraceWrappedContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ObjectBindingPattern || Rules.IsSingleLineBlockContext(context); - } - - // This check is done before an open brace in a control construct, a function, or a typescript block declaration - static IsBeforeMultilineBlockContext(context: FormattingContext): boolean { - return Rules.IsBeforeBlockContext(context) && !(context.NextNodeAllOnSameLine() || context.NextNodeBlockIsOnOneLine()); - } - - static IsMultilineBlockContext(context: FormattingContext): boolean { - return Rules.IsBlockContext(context) && !(context.ContextNodeAllOnSameLine() || context.ContextNodeBlockIsOnOneLine()); - } - - static IsSingleLineBlockContext(context: FormattingContext): boolean { - return Rules.IsBlockContext(context) && (context.ContextNodeAllOnSameLine() || context.ContextNodeBlockIsOnOneLine()); - } - - static IsBlockContext(context: FormattingContext): boolean { - return Rules.NodeIsBlockContext(context.contextNode); - } - - static IsBeforeBlockContext(context: FormattingContext): boolean { - return Rules.NodeIsBlockContext(context.nextTokenParent); - } - - // IMPORTANT!!! This method must return true ONLY for nodes with open and close braces as immediate children - static NodeIsBlockContext(node: Node): boolean { - if (Rules.NodeIsTypeScriptDeclWithBlockContext(node)) { - // 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). + switch (context.contextNode.kind) { + case SyntaxKind.BinaryExpression: + case SyntaxKind.ConditionalExpression: + case SyntaxKind.AsExpression: + case SyntaxKind.ExportSpecifier: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.TypePredicate: + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: return true; - } - switch (node.kind) { - case SyntaxKind.Block: - case SyntaxKind.CaseBlock: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.ModuleBlock: + // equals in binding elements: function foo([[x, y] = [1, 2]]) + case SyntaxKind.BindingElement: + // equals in type X = ... + case SyntaxKind.TypeAliasDeclaration: + // equal in import a = module('a'); + case SyntaxKind.ImportEqualsDeclaration: + // equal in let a = 0; + case SyntaxKind.VariableDeclaration: + // equal in p = 0; + case SyntaxKind.Parameter: + case SyntaxKind.EnumMember: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + return context.currentTokenSpan.kind === SyntaxKind.EqualsToken || context.nextTokenSpan.kind === SyntaxKind.EqualsToken; + // "in" keyword in for (let x in []) { } + case SyntaxKind.ForInStatement: + // "in" keyword in [P in keyof T]: T[P] + case SyntaxKind.TypeParameter: + return context.currentTokenSpan.kind === SyntaxKind.InKeyword || context.nextTokenSpan.kind === SyntaxKind.InKeyword; + // Technically, "of" is not a binary operator, but format it the same way as "in" + case SyntaxKind.ForOfStatement: + return context.currentTokenSpan.kind === SyntaxKind.OfKeyword || context.nextTokenSpan.kind === SyntaxKind.OfKeyword; + } + return false; + } + + function isNotBinaryOpContext(context: FormattingContext): boolean { + return !isBinaryOpContext(context); + } + + function isConditionalOperatorContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ConditionalExpression; + } + + function isSameLineTokenOrBeforeBlockContext(context: FormattingContext): boolean { + return context.TokensAreOnSameLine() || isBeforeBlockContext(context); + } + + function isBraceWrappedContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ObjectBindingPattern || isSingleLineBlockContext(context); + } + + // This check is done before an open brace in a control construct, a function, or a typescript block declaration + function isBeforeMultilineBlockContext(context: FormattingContext): boolean { + return isBeforeBlockContext(context) && !(context.NextNodeAllOnSameLine() || context.NextNodeBlockIsOnOneLine()); + } + + function isMultilineBlockContext(context: FormattingContext): boolean { + return isBlockContext(context) && !(context.ContextNodeAllOnSameLine() || context.ContextNodeBlockIsOnOneLine()); + } + + function isSingleLineBlockContext(context: FormattingContext): boolean { + return isBlockContext(context) && (context.ContextNodeAllOnSameLine() || context.ContextNodeBlockIsOnOneLine()); + } + + function isBlockContext(context: FormattingContext): boolean { + return nodeIsBlockContext(context.contextNode); + } + + function isBeforeBlockContext(context: FormattingContext): boolean { + return nodeIsBlockContext(context.nextTokenParent); + } + + // IMPORTANT!!! This method must return true ONLY for nodes with open and close braces as immediate children + function nodeIsBlockContext(node: Node): boolean { + if (nodeIsTypeScriptDeclWithBlockContext(node)) { + // 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). + return true; + } + + switch (node.kind) { + case SyntaxKind.Block: + case SyntaxKind.CaseBlock: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ModuleBlock: + return true; + } + + return false; + } + + function isFunctionDeclContext(context: FormattingContext): boolean { + switch (context.contextNode.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + // case SyntaxKind.MemberFunctionDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + // case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.FunctionExpression: + case SyntaxKind.Constructor: + case SyntaxKind.ArrowFunction: + // case SyntaxKind.ConstructorDeclaration: + // case SyntaxKind.SimpleArrowFunctionExpression: + // case SyntaxKind.ParenthesizedArrowFunctionExpression: + case SyntaxKind.InterfaceDeclaration: // This one is not truly a function, but for formatting purposes, it acts just like one + return true; + } + + return false; + } + + function isFunctionDeclarationOrFunctionExpressionContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.FunctionDeclaration || context.contextNode.kind === SyntaxKind.FunctionExpression; + } + + function isTypeScriptDeclWithBlockContext(context: FormattingContext): boolean { + return nodeIsTypeScriptDeclWithBlockContext(context.contextNode); + } + + function nodeIsTypeScriptDeclWithBlockContext(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.TypeLiteral: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ExportDeclaration: + case SyntaxKind.NamedExports: + case SyntaxKind.ImportDeclaration: + case SyntaxKind.NamedImports: + return true; + } + + return false; + } + + function isAfterCodeBlockContext(context: FormattingContext): boolean { + switch (context.currentTokenParent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.CatchClause: + case SyntaxKind.ModuleBlock: + case SyntaxKind.SwitchStatement: + return true; + case SyntaxKind.Block: { + const blockParent = context.currentTokenParent.parent; + // In a codefix scenario, we can't rely on parents being set. So just always return true. + if (!blockParent || blockParent.kind !== SyntaxKind.ArrowFunction && blockParent.kind !== SyntaxKind.FunctionExpression) { return true; - } - - return false; - } - - static IsFunctionDeclContext(context: FormattingContext): boolean { - switch (context.contextNode.kind) { - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - // case SyntaxKind.MemberFunctionDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - // case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.FunctionExpression: - case SyntaxKind.Constructor: - case SyntaxKind.ArrowFunction: - // case SyntaxKind.ConstructorDeclaration: - // case SyntaxKind.SimpleArrowFunctionExpression: - // case SyntaxKind.ParenthesizedArrowFunctionExpression: - case SyntaxKind.InterfaceDeclaration: // This one is not truly a function, but for formatting purposes, it acts just like one - return true; - } - - return false; - } - - static IsFunctionDeclarationOrFunctionExpressionContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.FunctionDeclaration || context.contextNode.kind === SyntaxKind.FunctionExpression; - } - - static IsTypeScriptDeclWithBlockContext(context: FormattingContext): boolean { - return Rules.NodeIsTypeScriptDeclWithBlockContext(context.contextNode); - } - - static NodeIsTypeScriptDeclWithBlockContext(node: Node): boolean { - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.TypeLiteral: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.ExportDeclaration: - case SyntaxKind.NamedExports: - case SyntaxKind.ImportDeclaration: - case SyntaxKind.NamedImports: - return true; - } - - return false; - } - - static IsAfterCodeBlockContext(context: FormattingContext): boolean { - switch (context.currentTokenParent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.CatchClause: - case SyntaxKind.ModuleBlock: - case SyntaxKind.SwitchStatement: - return true; - case SyntaxKind.Block: { - const blockParent = context.currentTokenParent.parent; - // In a codefix scenario, we can't rely on parents being set. So just always return true. - if (!blockParent || blockParent.kind !== SyntaxKind.ArrowFunction && blockParent.kind !== SyntaxKind.FunctionExpression) { - return true; - } } } - return false; } + return false; + } - static IsControlDeclContext(context: FormattingContext): boolean { - switch (context.contextNode.kind) { - case SyntaxKind.IfStatement: - case SyntaxKind.SwitchStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.TryStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WithStatement: - // TODO - // case SyntaxKind.ElseClause: - case SyntaxKind.CatchClause: - return true; + function isControlDeclContext(context: FormattingContext): boolean { + switch (context.contextNode.kind) { + case SyntaxKind.IfStatement: + case SyntaxKind.SwitchStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.TryStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WithStatement: + // TODO + // case SyntaxKind.ElseClause: + case SyntaxKind.CatchClause: + return true; - default: - return false; - } - } - - static IsObjectContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ObjectLiteralExpression; - } - - static IsFunctionCallContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.CallExpression; - } - - static IsNewContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.NewExpression; - } - - static IsFunctionCallOrNewContext(context: FormattingContext): boolean { - return Rules.IsFunctionCallContext(context) || Rules.IsNewContext(context); - } - - static IsPreviousTokenNotComma(context: FormattingContext): boolean { - return context.currentTokenSpan.kind !== SyntaxKind.CommaToken; - } - - static IsNextTokenNotCloseBracket(context: FormattingContext): boolean { - return context.nextTokenSpan.kind !== SyntaxKind.CloseBracketToken; - } - - static IsArrowFunctionContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ArrowFunction; - } - - static IsNonJsxSameLineTokenContext(context: FormattingContext): boolean { - return context.TokensAreOnSameLine() && context.contextNode.kind !== SyntaxKind.JsxText; - } - - static IsNonJsxElementContext(context: FormattingContext): boolean { - return context.contextNode.kind !== SyntaxKind.JsxElement; - } - - static IsJsxExpressionContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.JsxExpression; - } - - static IsNextTokenParentJsxAttribute(context: FormattingContext): boolean { - return context.nextTokenParent.kind === SyntaxKind.JsxAttribute; - } - - static IsJsxAttributeContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.JsxAttribute; - } - - static IsJsxSelfClosingElementContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.JsxSelfClosingElement; - } - - static IsNotBeforeBlockInFunctionDeclarationContext(context: FormattingContext): boolean { - return !Rules.IsFunctionDeclContext(context) && !Rules.IsBeforeBlockContext(context); - } - - static IsEndOfDecoratorContextOnSameLine(context: FormattingContext): boolean { - return context.TokensAreOnSameLine() && - context.contextNode.decorators && - Rules.NodeIsInDecoratorContext(context.currentTokenParent) && - !Rules.NodeIsInDecoratorContext(context.nextTokenParent); - } - - static NodeIsInDecoratorContext(node: Node): boolean { - while (isExpressionNode(node)) { - node = node.parent; - } - return node.kind === SyntaxKind.Decorator; - } - - static IsStartOfVariableDeclarationList(context: FormattingContext): boolean { - return context.currentTokenParent.kind === SyntaxKind.VariableDeclarationList && - context.currentTokenParent.getStart(context.sourceFile) === context.currentTokenSpan.pos; - } - - static IsNotFormatOnEnter(context: FormattingContext): boolean { - return context.formattingRequestKind !== FormattingRequestKind.FormatOnEnter; - } - - static IsModuleDeclContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ModuleDeclaration; - } - - static IsObjectTypeContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.TypeLiteral; // && context.contextNode.parent.kind !== SyntaxKind.InterfaceDeclaration; - } - - static IsConstructorSignatureContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.ConstructSignature; - } - - static IsTypeArgumentOrParameterOrAssertion(token: TextRangeWithKind, parent: Node): boolean { - if (token.kind !== SyntaxKind.LessThanToken && token.kind !== SyntaxKind.GreaterThanToken) { + default: return false; - } - switch (parent.kind) { - case SyntaxKind.TypeReference: - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.CallExpression: - case SyntaxKind.NewExpression: - case SyntaxKind.ExpressionWithTypeArguments: - return true; - default: - return false; - - } - } - - static IsTypeArgumentOrParameterOrAssertionContext(context: FormattingContext): boolean { - return Rules.IsTypeArgumentOrParameterOrAssertion(context.currentTokenSpan, context.currentTokenParent) || - Rules.IsTypeArgumentOrParameterOrAssertion(context.nextTokenSpan, context.nextTokenParent); - } - - static IsTypeAssertionContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.TypeAssertionExpression; - } - - static IsVoidOpContext(context: FormattingContext): boolean { - return context.currentTokenSpan.kind === SyntaxKind.VoidKeyword && context.currentTokenParent.kind === SyntaxKind.VoidExpression; - } - - static IsYieldOrYieldStarWithOperand(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.YieldExpression && (context.contextNode).expression !== undefined; - } - - static IsNonNullAssertionContext(context: FormattingContext): boolean { - return context.contextNode.kind === SyntaxKind.NonNullExpression; } } -} \ No newline at end of file + + function isObjectContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ObjectLiteralExpression; + } + + function isFunctionCallContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.CallExpression; + } + + function isNewContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.NewExpression; + } + + function isFunctionCallOrNewContext(context: FormattingContext): boolean { + return isFunctionCallContext(context) || isNewContext(context); + } + + function isPreviousTokenNotComma(context: FormattingContext): boolean { + return context.currentTokenSpan.kind !== SyntaxKind.CommaToken; + } + + function isNextTokenNotCloseBracket(context: FormattingContext): boolean { + return context.nextTokenSpan.kind !== SyntaxKind.CloseBracketToken; + } + + function isArrowFunctionContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ArrowFunction; + } + + function isNonJsxSameLineTokenContext(context: FormattingContext): boolean { + return context.TokensAreOnSameLine() && context.contextNode.kind !== SyntaxKind.JsxText; + } + + function isNonJsxElementContext(context: FormattingContext): boolean { + return context.contextNode.kind !== SyntaxKind.JsxElement; + } + + function isJsxExpressionContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.JsxExpression; + } + + function isNextTokenParentJsxAttribute(context: FormattingContext): boolean { + return context.nextTokenParent.kind === SyntaxKind.JsxAttribute; + } + + function isJsxAttributeContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.JsxAttribute; + } + + function isJsxSelfClosingElementContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.JsxSelfClosingElement; + } + + function isNotBeforeBlockInFunctionDeclarationContext(context: FormattingContext): boolean { + return !isFunctionDeclContext(context) && !isBeforeBlockContext(context); + } + + function isEndOfDecoratorContextOnSameLine(context: FormattingContext): boolean { + return context.TokensAreOnSameLine() && + context.contextNode.decorators && + nodeIsInDecoratorContext(context.currentTokenParent) && + !nodeIsInDecoratorContext(context.nextTokenParent); + } + + function nodeIsInDecoratorContext(node: Node): boolean { + while (isExpressionNode(node)) { + node = node.parent; + } + return node.kind === SyntaxKind.Decorator; + } + + function isStartOfVariableDeclarationList(context: FormattingContext): boolean { + return context.currentTokenParent.kind === SyntaxKind.VariableDeclarationList && + context.currentTokenParent.getStart(context.sourceFile) === context.currentTokenSpan.pos; + } + + function isNotFormatOnEnter(context: FormattingContext): boolean { + return context.formattingRequestKind !== FormattingRequestKind.FormatOnEnter; + } + + function isModuleDeclContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ModuleDeclaration; + } + + function isObjectTypeContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.TypeLiteral; // && context.contextNode.parent.kind !== SyntaxKind.InterfaceDeclaration; + } + + function isConstructorSignatureContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.ConstructSignature; + } + + function isTypeArgumentOrParameterOrAssertion(token: TextRangeWithKind, parent: Node): boolean { + if (token.kind !== SyntaxKind.LessThanToken && token.kind !== SyntaxKind.GreaterThanToken) { + return false; + } + switch (parent.kind) { + case SyntaxKind.TypeReference: + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + case SyntaxKind.ExpressionWithTypeArguments: + return true; + default: + return false; + + } + } + + function isTypeArgumentOrParameterOrAssertionContext(context: FormattingContext): boolean { + return isTypeArgumentOrParameterOrAssertion(context.currentTokenSpan, context.currentTokenParent) || + isTypeArgumentOrParameterOrAssertion(context.nextTokenSpan, context.nextTokenParent); + } + + function isTypeAssertionContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.TypeAssertionExpression; + } + + function isVoidOpContext(context: FormattingContext): boolean { + return context.currentTokenSpan.kind === SyntaxKind.VoidKeyword && context.currentTokenParent.kind === SyntaxKind.VoidExpression; + } + + function isYieldOrYieldStarWithOperand(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.YieldExpression && (context.contextNode).expression !== undefined; + } + + function isNonNullAssertionContext(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.NonNullExpression; + } +} diff --git a/src/services/formatting/rulesMap.ts b/src/services/formatting/rulesMap.ts index 3b04308ebe8..d44e47a763a 100644 --- a/src/services/formatting/rulesMap.ts +++ b/src/services/formatting/rulesMap.ts @@ -1,60 +1,59 @@ -/// +/// /* @internal */ namespace ts.formatting { - export class RulesMap { - public map: RulesBucket[]; - public mapRowLength: number; + export function getFormatContext(options: FormatCodeSettings): formatting.FormatContext { + return { options, getRule: getRulesMap() }; + } - constructor(rules: ReadonlyArray) { - this.mapRowLength = SyntaxKind.LastToken + 1; - this.map = new Array(this.mapRowLength * this.mapRowLength); + let rulesMapCache: RulesMap | undefined; - // This array is used only during construction of the rulesbucket in the map - const rulesBucketConstructionStateList: RulesBucketConstructionState[] = new Array(this.map.length); - for (const rule of rules) { - this.FillRule(rule, rulesBucketConstructionStateList); - } + function getRulesMap(): RulesMap { + if (rulesMapCache === undefined) { + rulesMapCache = createRulesMap(getAllRules()); } + return rulesMapCache; + } - private GetRuleBucketIndex(row: number, column: number): number { - Debug.assert(row <= SyntaxKind.LastKeyword && column <= SyntaxKind.LastKeyword, "Must compute formatting context from tokens"); - return (row * this.mapRowLength) + column; - } + export type RulesMap = (context: FormattingContext) => Rule | undefined; + function createRulesMap(rules: ReadonlyArray): 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))); + }; + } - private FillRule(rule: Rule, rulesBucketConstructionStateList: RulesBucketConstructionState[]): void { - const specificRule = rule.descriptor.leftTokenRange.isSpecific() && rule.descriptor.rightTokenRange.isSpecific(); + function buildMap(rules: ReadonlyArray): ReadonlyArray> { + // Map from bucket index to array of rules + const map: Rule[][] = new Array(mapRowLength * mapRowLength); + // This array is used only during construction of the rulesbucket in the map + const rulesBucketConstructionStateList = new Array(map.length); + for (const rule of rules) { + const specificRule = rule.leftTokenRange.isSpecific && rule.rightTokenRange.isSpecific; - rule.descriptor.leftTokenRange.GetTokens().forEach((left) => { - rule.descriptor.rightTokenRange.GetTokens().forEach((right) => { - const rulesBucketIndex = this.GetRuleBucketIndex(left, right); - - let rulesBucket = this.map[rulesBucketIndex]; + for (const left of rule.leftTokenRange.tokens) { + for (const right of rule.rightTokenRange.tokens) { + const index = getRuleBucketIndex(left, right); + let rulesBucket = map[index]; if (rulesBucket === undefined) { - rulesBucket = this.map[rulesBucketIndex] = new RulesBucket(); - } - - rulesBucket.AddRule(rule, specificRule, rulesBucketConstructionStateList, rulesBucketIndex); - }); - }); - } - - public GetRule(context: FormattingContext): Rule | undefined { - const bucketIndex = this.GetRuleBucketIndex(context.currentTokenSpan.kind, context.nextTokenSpan.kind); - const bucket = this.map[bucketIndex]; - if (bucket) { - for (const rule of bucket.Rules()) { - if (rule.operation.context.InContext(context)) { - return rule; + rulesBucket = map[index] = []; } + addRule(rulesBucket, rule.rule, specificRule, rulesBucketConstructionStateList, index); } } - return undefined; } + return map; + } + + function getRuleBucketIndex(row: number, column: number): number { + Debug.assert(row <= SyntaxKind.LastKeyword && column <= SyntaxKind.LastKeyword, "Must compute formatting context from tokens"); + return (row * mapRowLength) + column; } const maskBitSize = 5; - const mask = 0x1f; + const mask = 0b11111; // MaskBitSize bits + const mapRowLength = SyntaxKind.LastToken + 1; enum RulesPosition { IgnoreRulesSpecific = 0, @@ -65,92 +64,44 @@ namespace ts.formatting { NoContextRulesAny = maskBitSize * 5 } - export class RulesBucketConstructionState { - private rulesInsertionIndexBitmap: number; - - constructor() { - //// The Rules list contains all the inserted rules into a rulebucket in the following order: - //// 1- Ignore rules with specific token combination - //// 2- Ignore rules with any token combination - //// 3- Context rules with specific token combination - //// 4- Context rules with any token combination - //// 5- Non-context rules with specific token combination - //// 6- Non-context rules with any token combination - //// - //// The member rulesInsertionIndexBitmap is used to describe the number of rules - //// in each sub-bucket (above) hence can be used to know the index of where to insert - //// the next rule. It's a bitmap which contains 6 different sections each is given 5 bits. - //// - //// Example: - //// 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. - this.rulesInsertionIndexBitmap = 0; - } - - public GetInsertionIndex(maskPosition: RulesPosition): number { - let index = 0; - - let pos = 0; - let indexBitmap = this.rulesInsertionIndexBitmap; - - while (pos <= maskPosition) { - index += (indexBitmap & mask); - indexBitmap >>= maskBitSize; - pos += maskBitSize; - } - - return index; - } - - public IncreaseInsertionIndex(maskPosition: RulesPosition): void { - let value = (this.rulesInsertionIndexBitmap >> maskPosition) & mask; - value++; - Debug.assert((value & mask) === value, "Adding more rules into the sub-bucket than allowed. Maximum allowed is 32 rules."); - - let temp = this.rulesInsertionIndexBitmap & ~(mask << maskPosition); - temp |= value << maskPosition; - - this.rulesInsertionIndexBitmap = temp; - } + // The Rules list contains all the inserted rules into a rulebucket in the following order: + // 1- Ignore rules with specific token combination + // 2- Ignore rules with any token combination + // 3- Context rules with specific token combination + // 4- Context rules with any token combination + // 5- Non-context rules with specific token combination + // 6- Non-context rules with any token combination + // + // The member rulesInsertionIndexBitmap is used to describe the number of rules + // in each sub-bucket (above) hence can be used to know the index of where to insert + // the next rule. It's a bitmap which contains 6 different sections each is given 5 bits. + // + // Example: + // 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 + : rule.context !== anyContext + ? specificTokens ? RulesPosition.ContextRulesSpecific : RulesPosition.ContextRulesAny + : specificTokens ? RulesPosition.NoContextRulesSpecific : RulesPosition.NoContextRulesAny; + const state = constructionState[rulesBucketIndex] || 0; + rules.splice(getInsertionIndex(state, position), 0, rule); + constructionState[rulesBucketIndex] = increaseInsertionIndex(state, position); } - export class RulesBucket { - private rules: Rule[]; - - constructor() { - this.rules = []; + function getInsertionIndex(indexBitmap: number, maskPosition: RulesPosition) { + let index = 0; + for (let pos = 0; pos <= maskPosition; pos += maskBitSize) { + index += indexBitmap & mask; + indexBitmap >>= maskBitSize; } + return index; + } - public Rules(): Rule[] { - return this.rules; - } - - public AddRule(rule: Rule, specificTokens: boolean, constructionState: RulesBucketConstructionState[], rulesBucketIndex: number): void { - let position: RulesPosition; - - if (rule.operation.action === RuleAction.Ignore) { - position = specificTokens ? - RulesPosition.IgnoreRulesSpecific : - RulesPosition.IgnoreRulesAny; - } - else if (!rule.operation.context.IsAny()) { - position = specificTokens ? - RulesPosition.ContextRulesSpecific : - RulesPosition.ContextRulesAny; - } - else { - position = specificTokens ? - RulesPosition.NoContextRulesSpecific : - RulesPosition.NoContextRulesAny; - } - - let state = constructionState[rulesBucketIndex]; - if (state === undefined) { - state = constructionState[rulesBucketIndex] = new RulesBucketConstructionState(); - } - const index = state.GetInsertionIndex(position); - this.rules.splice(index, 0, rule); - state.IncreaseInsertionIndex(position); - } + function increaseInsertionIndex(indexBitmap: number, maskPosition: RulesPosition): number { + const value = ((indexBitmap >> maskPosition) & mask) + 1; + Debug.assert((value & mask) === value, "Adding more rules into the sub-bucket than allowed. Maximum allowed is 32 rules."); + return (indexBitmap & ~(mask << maskPosition)) | (value << maskPosition); } } \ No newline at end of file diff --git a/src/services/formatting/rulesProvider.ts b/src/services/formatting/rulesProvider.ts deleted file mode 100644 index fcf08541890..00000000000 --- a/src/services/formatting/rulesProvider.ts +++ /dev/null @@ -1,30 +0,0 @@ -/// - -/* @internal */ -namespace ts.formatting { - export class RulesProvider { - private globalRules: Rules; - private options: ts.FormatCodeSettings; - private rulesMap: RulesMap; - - constructor() { - this.globalRules = new Rules(); - const activeRules = this.globalRules.HighPriorityCommonRules.concat(this.globalRules.UserConfigurableRules).concat(this.globalRules.LowPriorityCommonRules); - this.rulesMap = new RulesMap(activeRules); - } - - public getRulesMap() { - return this.rulesMap; - } - - public getFormatOptions(): Readonly { - return this.options; - } - - public ensureUpToDate(options: ts.FormatCodeSettings) { - if (!this.options || !ts.compareDataObjects(this.options, options)) { - this.options = ts.clone(options); - } - } - } -} \ No newline at end of file diff --git a/src/services/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts index 7701cff182c..5afe4cb1d31 100644 --- a/src/services/formatting/smartIndenter.ts +++ b/src/services/formatting/smartIndenter.ts @@ -1,5 +1,3 @@ -/// - /* @internal */ namespace ts.formatting { export namespace SmartIndenter { diff --git a/src/services/formatting/tokenRange.ts b/src/services/formatting/tokenRange.ts deleted file mode 100644 index 31e15bc738d..00000000000 --- a/src/services/formatting/tokenRange.ts +++ /dev/null @@ -1,124 +0,0 @@ -/// - -/* @internal */ -namespace ts.formatting { - export namespace Shared { - const allTokens: SyntaxKind[] = []; - for (let token = SyntaxKind.FirstToken; token <= SyntaxKind.LastToken; token++) { - allTokens.push(token); - } - - class TokenValuesAccess implements TokenRange { - constructor(private readonly tokens: SyntaxKind[] = []) { } - - public GetTokens(): SyntaxKind[] { - return this.tokens; - } - - public Contains(token: SyntaxKind): boolean { - return this.tokens.indexOf(token) >= 0; - } - - public isSpecific() { return true; } - } - - class TokenSingleValueAccess implements TokenRange { - constructor(private readonly token: SyntaxKind) {} - - public GetTokens(): SyntaxKind[] { - return [this.token]; - } - - public Contains(tokenValue: SyntaxKind): boolean { - return tokenValue === this.token; - } - - public isSpecific() { return true; } - } - - class TokenAllAccess implements TokenRange { - public GetTokens(): SyntaxKind[] { - return allTokens; - } - - public Contains(): boolean { - return true; - } - - public toString(): string { - return "[allTokens]"; - } - - public isSpecific() { return false; } - } - - class TokenAllExceptAccess implements TokenRange { - constructor(private readonly except: SyntaxKind) {} - - public GetTokens(): SyntaxKind[] { - return allTokens.filter(t => t !== this.except); - } - - public Contains(token: SyntaxKind): boolean { - return token !== this.except; - } - - public isSpecific() { return false; } - } - - export interface TokenRange { - GetTokens(): SyntaxKind[]; - Contains(token: SyntaxKind): boolean; - isSpecific(): boolean; - } - - export namespace TokenRange { - export function FromToken(token: SyntaxKind): TokenRange { - return new TokenSingleValueAccess(token); - } - - export function FromTokens(tokens: SyntaxKind[]): TokenRange { - return new TokenValuesAccess(tokens); - } - - export function FromRange(from: SyntaxKind, to: SyntaxKind, except: SyntaxKind[] = []): TokenRange { - const tokens: SyntaxKind[] = []; - for (let token = from; token <= to; token++) { - if (ts.indexOf(except, token) < 0) { - tokens.push(token); - } - } - return new TokenValuesAccess(tokens); - } - - export function AnyExcept(token: SyntaxKind): TokenRange { - return new TokenAllExceptAccess(token); - } - - // tslint:disable variable-name (TODO) - export const Any: TokenRange = new TokenAllAccess(); - export const AnyIncludingMultilineComments = TokenRange.FromTokens([...allTokens, SyntaxKind.MultiLineCommentTrivia]); - export const Keywords = TokenRange.FromRange(SyntaxKind.FirstKeyword, SyntaxKind.LastKeyword); - export const BinaryOperators = TokenRange.FromRange(SyntaxKind.FirstBinaryOperator, SyntaxKind.LastBinaryOperator); - export const BinaryKeywordOperators = TokenRange.FromTokens([ - SyntaxKind.InKeyword, SyntaxKind.InstanceOfKeyword, SyntaxKind.OfKeyword, SyntaxKind.AsKeyword, SyntaxKind.IsKeyword]); - export const UnaryPrefixOperators = TokenRange.FromTokens([ - SyntaxKind.PlusPlusToken, SyntaxKind.MinusMinusToken, SyntaxKind.TildeToken, SyntaxKind.ExclamationToken]); - export const UnaryPrefixExpressions = TokenRange.FromTokens([ - SyntaxKind.NumericLiteral, SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.OpenBracketToken, - SyntaxKind.OpenBraceToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]); - export const UnaryPreincrementExpressions = TokenRange.FromTokens([ - SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]); - export const UnaryPostincrementExpressions = TokenRange.FromTokens([ - SyntaxKind.Identifier, SyntaxKind.CloseParenToken, SyntaxKind.CloseBracketToken, SyntaxKind.NewKeyword]); - export const UnaryPredecrementExpressions = TokenRange.FromTokens([ - SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]); - export const UnaryPostdecrementExpressions = TokenRange.FromTokens([ - SyntaxKind.Identifier, SyntaxKind.CloseParenToken, SyntaxKind.CloseBracketToken, SyntaxKind.NewKeyword]); - export const Comments = TokenRange.FromTokens([SyntaxKind.SingleLineCommentTrivia, SyntaxKind.MultiLineCommentTrivia]); - export const TypeNames = TokenRange.FromTokens([ - SyntaxKind.Identifier, SyntaxKind.NumberKeyword, SyntaxKind.StringKeyword, SyntaxKind.BooleanKeyword, - SyntaxKind.SymbolKeyword, SyntaxKind.VoidKeyword, SyntaxKind.AnyKeyword]); - } - } -} diff --git a/src/services/refactors/convertFunctionToEs6Class.ts b/src/services/refactors/convertFunctionToEs6Class.ts index f5951bd26e4..b66d14ee449 100644 --- a/src/services/refactors/convertFunctionToEs6Class.ts +++ b/src/services/refactors/convertFunctionToEs6Class.ts @@ -50,7 +50,7 @@ namespace ts.refactor.convertFunctionToES6Class { const { file: sourceFile } = context; const ctorSymbol = getConstructorSymbol(context); - const newLine = context.rulesProvider.getFormatOptions().newLineCharacter; + const newLine = context.formatContext.options.newLineCharacter; const deletedNodes: Node[] = []; const deletes: (() => any)[] = []; diff --git a/src/services/services.ts b/src/services/services.ts index 04a8e148b73..37298c8eba8 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -33,9 +33,6 @@ namespace ts { /** The version of the language service API */ export const servicesVersion = "0.7"; - /* @internal */ - let ruleProvider: formatting.RulesProvider; - function createNode(kind: TKind, pos: number, end: number, parent?: Node): NodeObject | TokenObject | IdentifierObject { const node = isNodeKind(kind) ? new NodeObject(kind, pos, end) : kind === SyntaxKind.Identifier ? new IdentifierObject(SyntaxKind.Identifier, pos, end) : @@ -1157,7 +1154,6 @@ namespace ts { documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory())): LanguageService { const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); - ruleProvider = ruleProvider || new formatting.RulesProvider(); let program: Program; let lastProjectVersion: string; let lastTypesRootVersion = 0; @@ -1187,11 +1183,6 @@ namespace ts { return sourceFile; } - function getRuleProvider(options: FormatCodeSettings) { - ruleProvider.ensureUpToDate(options); - return ruleProvider; - } - function synchronizeHostData(): void { // perform fast check if host supports it if (host.getProjectVersion) { @@ -1429,7 +1420,6 @@ namespace ts { function getCompletionEntryDetails(fileName: string, position: number, name: string, formattingOptions?: FormatCodeSettings, source?: string): CompletionEntryDetails { synchronizeHostData(); - const ruleProvider = formattingOptions ? getRuleProvider(formattingOptions) : undefined; return Completions.getCompletionEntryDetails( program.getTypeChecker(), log, @@ -1439,7 +1429,7 @@ namespace ts { { name, source }, program.getSourceFiles(), host, - ruleProvider); + formattingOptions && formatting.getFormatContext(formattingOptions)); } function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string): Symbol { @@ -1838,32 +1828,27 @@ namespace ts { function getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const settings = toEditorSettings(options); - return formatting.formatSelection(start, end, sourceFile, getRuleProvider(settings), settings); + return formatting.formatSelection(start, end, sourceFile, formatting.getFormatContext(toEditorSettings(options))); } function getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { - const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const settings = toEditorSettings(options); - return formatting.formatDocument(sourceFile, getRuleProvider(settings), settings); + return formatting.formatDocument(syntaxTreeCache.getCurrentSourceFile(fileName), formatting.getFormatContext(toEditorSettings(options))); } function getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] { const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const settings = toEditorSettings(options); + const formatContext = formatting.getFormatContext(toEditorSettings(options)); if (!isInComment(sourceFile, position)) { - if (key === "{") { - return formatting.formatOnOpeningCurly(position, sourceFile, getRuleProvider(settings), settings); - } - else if (key === "}") { - return formatting.formatOnClosingCurly(position, sourceFile, getRuleProvider(settings), settings); - } - else if (key === ";") { - return formatting.formatOnSemicolon(position, sourceFile, getRuleProvider(settings), settings); - } - else if (key === "\n") { - return formatting.formatOnEnter(position, sourceFile, getRuleProvider(settings), settings); + switch (key) { + case "{": + return formatting.formatOnOpeningCurly(position, sourceFile, formatContext); + case "}": + return formatting.formatOnClosingCurly(position, sourceFile, formatContext); + case ";": + return formatting.formatOnSemicolon(position, sourceFile, formatContext); + case "\n": + return formatting.formatOnEnter(position, sourceFile, formatContext); } } @@ -1875,11 +1860,11 @@ namespace ts { const sourceFile = getValidSourceFile(fileName); const span = createTextSpanFromBounds(start, end); const newLineCharacter = getNewLineOrDefaultFromHost(host); - const rulesProvider = getRuleProvider(formatOptions); + const formatContext = formatting.getFormatContext(formatOptions); return flatMap(deduplicate(errorCodes, equateValues, compareValues), errorCode => { cancellationToken.throwIfCancellationRequested(); - return codefix.getFixes({ errorCode, sourceFile, span, program, newLineCharacter, host, cancellationToken, rulesProvider }); + return codefix.getFixes({ errorCode, sourceFile, span, program, newLineCharacter, host, cancellationToken, formatContext }); }); } @@ -2104,7 +2089,7 @@ namespace ts { program: getProgram(), newLineCharacter: formatOptions ? formatOptions.newLineCharacter : host.getNewLine(), host, - rulesProvider: getRuleProvider(formatOptions), + formatContext: formatting.getFormatContext(formatOptions), cancellationToken, }; } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index bc3a8e27ef0..7c4e25537ef 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -188,7 +188,7 @@ namespace ts.textChanges { export interface TextChangesContext { newLineCharacter: string; - rulesProvider: formatting.RulesProvider; + formatContext: ts.formatting.FormatContext; } export class ChangeTracker { @@ -196,7 +196,7 @@ namespace ts.textChanges { private readonly newLineCharacter: string; public static fromContext(context: TextChangesContext): ChangeTracker { - return new ChangeTracker(context.newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed, context.rulesProvider); + return new ChangeTracker(context.newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed, context.formatContext); } public static with(context: TextChangesContext, cb: (tracker: ChangeTracker) => void): FileTextChanges[] { @@ -207,7 +207,7 @@ namespace ts.textChanges { constructor( private readonly newLine: NewLineKind, - private readonly rulesProvider: formatting.RulesProvider, + private readonly formatContext: ts.formatting.FormatContext, private readonly validator?: (text: NonFormattedText) => void) { this.newLineCharacter = getNewLineCharacter({ newLine }); } @@ -475,7 +475,7 @@ namespace ts.textChanges { options: {} }); // use the same indentation as 'after' item - const indentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(afterStartLinePosition, afterStart, sourceFile, this.rulesProvider.getFormatOptions()); + const indentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(afterStartLinePosition, afterStart, sourceFile, this.formatContext.options); // insert element before the line break on the line that contains 'after' element let insertPos = skipTrivia(sourceFile.text, end, /*stopAfterLineBreak*/ true, /*stopAtComments*/ false); if (insertPos !== end && isLineBreak(sourceFile.text.charCodeAt(insertPos - 1))) { @@ -562,7 +562,7 @@ namespace ts.textChanges { this.validator(nonformattedText); } - const formatOptions = this.rulesProvider.getFormatOptions(); + const { options: formatOptions } = this.formatContext; const posStartsLine = getLineStartPositionForPosition(pos, sourceFile) === pos; const initialIndentation = @@ -578,7 +578,7 @@ namespace ts.textChanges { ? (formatOptions.indentSize || 0) : 0; - return applyFormatting(nonformattedText, sourceFile, initialIndentation, delta, this.rulesProvider); + return applyFormatting(nonformattedText, sourceFile, initialIndentation, delta, this.formatContext); } private static normalize(changes: Change[]): Change[] { @@ -605,14 +605,14 @@ namespace ts.textChanges { return { text: writer.getText(), node: assignPositionsToNode(node) }; } - function applyFormatting(nonFormattedText: NonFormattedText, sourceFile: SourceFile, initialIndentation: number, delta: number, rulesProvider: formatting.RulesProvider) { + function applyFormatting(nonFormattedText: NonFormattedText, sourceFile: SourceFile, initialIndentation: number, delta: number, formatContext: ts.formatting.FormatContext) { const lineMap = computeLineStarts(nonFormattedText.text); const file: SourceFileLike = { text: nonFormattedText.text, lineMap, getLineAndCharacterOfPosition: pos => computeLineAndCharacterOfPosition(lineMap, pos) }; - const changes = formatting.formatNodeGivenIndentation(nonFormattedText.node, file, sourceFile.languageVariant, initialIndentation, delta, rulesProvider); + const changes = formatting.formatNodeGivenIndentation(nonFormattedText.node, file, sourceFile.languageVariant, initialIndentation, delta, formatContext); return applyChanges(nonFormattedText.text, changes); } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index f59f6e56e91..1ed44d29f95 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1068,20 +1068,19 @@ namespace ts { return createTextSpanFromBounds(range.pos, range.end); } + export const typeKeywords: ReadonlyArray = [ + SyntaxKind.AnyKeyword, + SyntaxKind.BooleanKeyword, + SyntaxKind.NeverKeyword, + SyntaxKind.NumberKeyword, + SyntaxKind.ObjectKeyword, + SyntaxKind.StringKeyword, + SyntaxKind.SymbolKeyword, + SyntaxKind.VoidKeyword, + ]; + export function isTypeKeyword(kind: SyntaxKind): boolean { - switch (kind) { - case SyntaxKind.AnyKeyword: - case SyntaxKind.BooleanKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.ObjectKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.VoidKeyword: - return true; - default: - return false; - } + return contains(typeKeywords, kind); } /** True if the symbol is for an external module, as opposed to a namespace. */