mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-26 10:43:51 -05:00
initial revision of formatting
This commit is contained in:
@@ -132,7 +132,7 @@ module ts {
|
||||
function writeLiteral(s: string) {
|
||||
if (s && s.length) {
|
||||
write(s);
|
||||
var lineStartsOfS = getLineStarts(s);
|
||||
var lineStartsOfS = computeLineStarts(s);
|
||||
if (lineStartsOfS.length > 1) {
|
||||
lineCount = lineCount + lineStartsOfS.length - 1;
|
||||
linePos = output.length - s.length + lineStartsOfS[lineStartsOfS.length - 1];
|
||||
|
||||
@@ -782,18 +782,16 @@ module ts {
|
||||
};
|
||||
})();
|
||||
|
||||
function getLineAndCharacterlFromSourcePosition(position: number) {
|
||||
if (!lineStarts) {
|
||||
lineStarts = getLineStarts(sourceText);
|
||||
}
|
||||
return getLineAndCharacterOfPosition(lineStarts, position);
|
||||
function getLineStarts(): number[] {
|
||||
return lineStarts || (lineStarts = computeLineStarts(sourceText));
|
||||
}
|
||||
|
||||
function getLineAndCharacterFromSourcePosition(position: number) {
|
||||
return getLineAndCharacterOfPosition(getLineStarts(), position);
|
||||
}
|
||||
|
||||
function getPositionFromSourceLineAndCharacter(line: number, character: number): number {
|
||||
if (!lineStarts) {
|
||||
lineStarts = getLineStarts(sourceText);
|
||||
}
|
||||
return getPositionFromLineAndCharacter(lineStarts, line, character);
|
||||
return getPositionFromLineAndCharacter(getLineStarts(), line, character);
|
||||
}
|
||||
|
||||
function error(message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): void {
|
||||
@@ -3888,8 +3886,9 @@ module ts {
|
||||
file = <SourceFile>createRootNode(SyntaxKind.SourceFile, 0, sourceText.length, rootNodeFlags);
|
||||
file.filename = normalizePath(filename);
|
||||
file.text = sourceText;
|
||||
file.getLineAndCharacterFromPosition = getLineAndCharacterlFromSourcePosition;
|
||||
file.getLineAndCharacterFromPosition = getLineAndCharacterFromSourcePosition;
|
||||
file.getPositionFromLineAndCharacter = getPositionFromSourceLineAndCharacter;
|
||||
file.getLineStarts = getLineStarts;
|
||||
file.syntacticErrors = [];
|
||||
file.semanticErrors = [];
|
||||
var referenceComments = processReferenceComments();
|
||||
|
||||
@@ -244,7 +244,7 @@ module ts {
|
||||
return tokenStrings[t];
|
||||
}
|
||||
|
||||
export function getLineStarts(text: string): number[] {
|
||||
export function computeLineStarts(text: string): number[] {
|
||||
var result: number[] = new Array();
|
||||
var pos = 0;
|
||||
var lineStart = 0;
|
||||
@@ -292,7 +292,7 @@ module ts {
|
||||
}
|
||||
|
||||
export function positionToLineAndCharacter(text: string, pos: number) {
|
||||
var lineStarts = getLineStarts(text);
|
||||
var lineStarts = computeLineStarts(text);
|
||||
return getLineAndCharacterOfPosition(lineStarts, pos);
|
||||
}
|
||||
|
||||
|
||||
@@ -547,6 +547,7 @@ module ts {
|
||||
text: string;
|
||||
getLineAndCharacterFromPosition(position: number): { line: number; character: number };
|
||||
getPositionFromLineAndCharacter(line: number, character: number): number;
|
||||
getLineStarts(): number[];
|
||||
amdDependencies: string[];
|
||||
referencedFiles: FileReference[];
|
||||
syntacticErrors: Diagnostic[];
|
||||
|
||||
@@ -882,7 +882,7 @@ module Harness {
|
||||
// Note: IE JS engine incorrectly handles consecutive delimiters here when using RegExp split, so
|
||||
// we have to string-based splitting instead and try to figure out the delimiting chars
|
||||
|
||||
var lineStarts = ts.getLineStarts(inputFile.content);
|
||||
var lineStarts = ts.computeLineStarts(inputFile.content);
|
||||
var lines = inputFile.content.split('\n');
|
||||
lines.forEach((line, lineIndex) => {
|
||||
if (line.length > 0 && line.charAt(line.length - 1) === '\r') {
|
||||
|
||||
@@ -223,7 +223,7 @@ module Harness.SourceMapRecoder {
|
||||
sourceMapNames = sourceMapData.sourceMapNames;
|
||||
|
||||
jsFile = currentJsFile;
|
||||
jsLineMap = ts.getLineStarts(jsFile.code);
|
||||
jsLineMap = ts.computeLineStarts(jsFile.code);
|
||||
|
||||
spansOnSingleLine = [];
|
||||
prevWrittenSourcePos = 0;
|
||||
@@ -294,7 +294,7 @@ module Harness.SourceMapRecoder {
|
||||
sourceMapRecoder.WriteLine("sourceFile:" + sourceMapSources[spansOnSingleLine[0].sourceMapSpan.sourceIndex]);
|
||||
sourceMapRecoder.WriteLine("-------------------------------------------------------------------");
|
||||
|
||||
tsLineMap = ts.getLineStarts(newSourceFileCode);
|
||||
tsLineMap = ts.computeLineStarts(newSourceFileCode);
|
||||
tsCode = newSourceFileCode;
|
||||
prevWrittenSourcePos = 0;
|
||||
}
|
||||
@@ -390,7 +390,7 @@ module Harness.SourceMapRecoder {
|
||||
}
|
||||
}
|
||||
|
||||
var tsCodeLineMap = ts.getLineStarts(sourceText);
|
||||
var tsCodeLineMap = ts.computeLineStarts(sourceText);
|
||||
for (var i = 0; i < tsCodeLineMap.length; i++) {
|
||||
writeSourceMapIndent(prevEmittedCol, i == 0 ? markerIds[index] : " >");
|
||||
sourceMapRecoder.Write(getTextOfLine(i, tsCodeLineMap, sourceText));
|
||||
|
||||
535
src/services/formatting/format.ts
Normal file
535
src/services/formatting/format.ts
Normal file
@@ -0,0 +1,535 @@
|
||||
///<reference path='..\services.ts' />
|
||||
///<reference path='stringUtilities.ts' />
|
||||
///<reference path='new\rulesProvider.ts' />
|
||||
|
||||
module ts.formatting {
|
||||
|
||||
export interface TextRangeWithKind extends TextRange {
|
||||
kind: SyntaxKind;
|
||||
}
|
||||
|
||||
export interface TokenInfo extends TextRange {
|
||||
leadingTrivia: TextRangeWithKind[];
|
||||
token: TextRangeWithKind;
|
||||
trailingTrivia: TextRangeWithKind[];
|
||||
pos: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
var formattingScanner = createScanner(ScriptTarget.ES5, /*skipTrivia*/ false);
|
||||
|
||||
export function formatOnEnter(position: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeOptions): TextChange[]{
|
||||
var line = getNonAdjustedLineAndCharacterFromPosition(position, sourceFile).line;
|
||||
// get the span for the previous\current line
|
||||
var span = {
|
||||
// get start position for the previous line
|
||||
pos: getStartPositionOfLine(line - 1, sourceFile),
|
||||
// get end position for the current line (end value is exclusive so add 1 to the result)
|
||||
end: getEndLinePosition(line, sourceFile) + 1
|
||||
}
|
||||
return formatSpan(span, sourceFile, options, rulesProvider, FormattingRequestKind.FormatOnEnter, formattingScanner);
|
||||
}
|
||||
|
||||
export function formatOnSemicolon(position: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeOptions): TextChange[]{
|
||||
return formatOutermostParent(position, SyntaxKind.SemicolonToken, sourceFile, options, rulesProvider, FormattingRequestKind.FormatOnSemicolon);
|
||||
}
|
||||
|
||||
export function formatOnClosingCurly(position: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeOptions): TextChange[] {
|
||||
return formatOutermostParent(position, SyntaxKind.CloseBraceToken, sourceFile, options, rulesProvider, FormattingRequestKind.FormatOnClosingCurlyBrace);
|
||||
}
|
||||
|
||||
export function formatDocument(sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeOptions): TextChange[]{
|
||||
var span = {
|
||||
pos: 0,
|
||||
end: sourceFile.text.length
|
||||
};
|
||||
return formatSpan(span, sourceFile, options, rulesProvider, FormattingRequestKind.FormatDocument, formattingScanner);
|
||||
}
|
||||
|
||||
export function formatSelection(start: number, end: number, sourceFile: SourceFile, rulesProvider: RulesProvider, options: FormatCodeOptions): TextChange[]{
|
||||
// format from the beginning of the line
|
||||
var span = {
|
||||
pos: getStartLinePositionForPosition(start, sourceFile),
|
||||
end: end
|
||||
};
|
||||
return formatSpan(span, sourceFile, options, rulesProvider, FormattingRequestKind.FormatSelection, formattingScanner);
|
||||
}
|
||||
|
||||
function getEndLinePosition(line: number, sourceFile: SourceFile): number {
|
||||
var lineStarts = sourceFile.getLineStarts();
|
||||
if (line === lineStarts.length - 1) {
|
||||
// last line - return EOF - <start pos of line>
|
||||
return sourceFile.text.length - lineStarts[line];
|
||||
}
|
||||
else {
|
||||
// current line start
|
||||
var start = lineStarts[line];
|
||||
// take the start position of the next line -1 = it should be some line break
|
||||
var pos = lineStarts[line + 1] - 1;
|
||||
Debug.assert(isLineBreak(sourceFile.text.charCodeAt(pos)));
|
||||
// walk backwards skipping line breaks, stop the the beginning of current line.
|
||||
// i.e:
|
||||
// <some text>
|
||||
// $ <- end of line for this position should match the start position
|
||||
while (start <= pos && isLineBreak(sourceFile.text.charCodeAt(pos))) {
|
||||
pos--;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
function getStartPositionOfLine(line: number, sourceFile: SourceFile): number {
|
||||
return sourceFile.getLineStarts()[line];
|
||||
}
|
||||
|
||||
function getStartLinePositionForPosition(position: number, sourceFile: SourceFile): number {
|
||||
var lineStarts = sourceFile.getLineStarts();
|
||||
var line = getNonAdjustedLineAndCharacterFromPosition(position, sourceFile).line;
|
||||
return lineStarts[line];
|
||||
}
|
||||
|
||||
function formatOutermostParent(position: number, expectedLastToken: SyntaxKind, sourceFile: SourceFile, options: FormatCodeOptions, rulesProvider: RulesProvider, requestKind: FormattingRequestKind): TextChange[]{
|
||||
var parent = findOutermostParent(position, expectedLastToken, sourceFile);
|
||||
if (!parent) {
|
||||
return [];
|
||||
}
|
||||
var span = {
|
||||
pos: getStartLinePositionForPosition(parent.pos, sourceFile),
|
||||
end: parent.end
|
||||
};
|
||||
return formatSpan(span, sourceFile, options, rulesProvider, requestKind, formattingScanner);
|
||||
}
|
||||
|
||||
function findOutermostParent(position: number, expectedTokenKind: SyntaxKind, sourceFile: SourceFile): Node {
|
||||
var precedingToken = findPrecedingToken(position, sourceFile);
|
||||
if (!precedingToken || precedingToken.kind !== expectedTokenKind) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// walk up and search for the parent node that ends at the same position with precedingToken
|
||||
var current = precedingToken;
|
||||
while (current &&
|
||||
current.parent &&
|
||||
current.parent.end === precedingToken.end &&
|
||||
!isListElement(current.parent, current) ) {
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
function rangeContainsRange(initial: TextRange, candidate: TextRange): boolean {
|
||||
return startEndContainsRange(initial.pos, initial.end, candidate);
|
||||
}
|
||||
|
||||
function startEndContainsRange(start: number, end: number, candidate: TextRange): boolean {
|
||||
return start <= candidate.pos && end >= candidate.end;
|
||||
}
|
||||
|
||||
function rangeOverlapsWithRange(r1: TextRange, r2: TextRange): boolean {
|
||||
var start = Math.max(r1.pos, r2.pos);
|
||||
var end = Math.min(r1.end, r2.end);
|
||||
return start < end;
|
||||
}
|
||||
|
||||
function isListElement(parent: Node, node: Node): boolean {
|
||||
switch (parent.kind) {
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
return rangeContainsRange((<InterfaceDeclaration>parent).members, node);
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
var body = (<ModuleDeclaration>parent).body;
|
||||
return body && body.kind === SyntaxKind.Block && rangeContainsRange((<Block>body).statements, node);
|
||||
case SyntaxKind.SourceFile:
|
||||
case SyntaxKind.Block:
|
||||
case SyntaxKind.TryBlock:
|
||||
case SyntaxKind.CatchBlock:
|
||||
case SyntaxKind.FinallyBlock:
|
||||
case SyntaxKind.ModuleBlock:
|
||||
return rangeContainsRange((<Block>parent).statements, node)
|
||||
}
|
||||
}
|
||||
|
||||
function findEnclosingNode(range: TextRange, sourceFile: SourceFile): Node {
|
||||
return find(sourceFile);
|
||||
|
||||
function find(n: Node): Node {
|
||||
var candidate = forEachChild(n, c => startEndContainsRange(c.getStart(sourceFile), c.end, range) && c);
|
||||
return (candidate && find(candidate)) || n;
|
||||
}
|
||||
}
|
||||
|
||||
function getIndentationForNode(n: Node, sourceFile: SourceFile, options: FormatCodeOptions): number {
|
||||
var start = sourceFile.getLineAndCharacterFromPosition(n.getStart(sourceFile));
|
||||
return SmartIndenter.getIndentationForNode(n, start, /*indentationDelta*/ 0, sourceFile, options);
|
||||
}
|
||||
|
||||
function getNonAdjustedLineAndCharacterFromPosition(position: number, sourceFile: SourceFile): LineAndCharacter {
|
||||
var lineAndChar = sourceFile.getLineAndCharacterFromPosition(position);
|
||||
return { line: lineAndChar.line - 1, character: lineAndChar.character - 1 };
|
||||
}
|
||||
|
||||
function formatSpan(originalRange: TextRange,
|
||||
sourceFile: SourceFile,
|
||||
options: FormatCodeOptions,
|
||||
rulesProvider: RulesProvider,
|
||||
requestKind: FormattingRequestKind,
|
||||
scanner: Scanner): TextChange[] {
|
||||
|
||||
// formatting context to be used by rules provider to get rules
|
||||
var formattingContext = new FormattingContext(sourceFile, requestKind);
|
||||
|
||||
var enclosingNode = findEnclosingNode(originalRange, sourceFile);
|
||||
var initialIndentation = getIndentationForNode(enclosingNode, sourceFile, options);
|
||||
|
||||
scanner.setText(sourceFile.text);
|
||||
scanner.setTextPos(enclosingNode.pos);
|
||||
|
||||
var previousRange: TextRangeWithKind;
|
||||
var previousParent: Node;
|
||||
var previousRangeStartLine: number;
|
||||
|
||||
var lastTriviaWasNewLine = false;
|
||||
var edits: TextChange[] = [];
|
||||
|
||||
// advance the scaner
|
||||
scanner.scan();
|
||||
var currentTokenInfo = fetchNextTokenInfo();
|
||||
|
||||
if (currentTokenInfo.token) {
|
||||
var startLine = getNonAdjustedLineAndCharacterFromPosition(enclosingNode.getStart(sourceFile), sourceFile).line;
|
||||
processNode(enclosingNode, enclosingNode, startLine, initialIndentation);
|
||||
}
|
||||
return edits;
|
||||
|
||||
function processNode(node: Node, contextNode: Node, nodeStartLine: number, indentation: number) {
|
||||
// TODO: skip nodes that has skipped or missing tokens
|
||||
if (!rangeOverlapsWithRange(originalRange, node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rangeContainsRange(node, currentTokenInfo)) {
|
||||
// node and its descendents don't contain current token from the scanner - skip it
|
||||
return;
|
||||
}
|
||||
|
||||
var childContextNode = contextNode;
|
||||
forEachChild(
|
||||
node,
|
||||
child => processChildNode(child, /*containingList*/ undefined, /*listElementIndex*/ -1),
|
||||
nodes => {
|
||||
for (var i = 0, len = nodes.length; i < len; ++i) {
|
||||
processChildNode(nodes[i], /*containingList*/ nodes, /*listElementIndex*/ i)
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
while (currentTokenInfo.token && node.end >= currentTokenInfo.token.end) {
|
||||
currentTokenInfo = consumeCurrentToken(node, childContextNode, indentation);
|
||||
}
|
||||
|
||||
/// Local functions
|
||||
|
||||
function processChildNode(child: Node, containingList: Node[], listElementIndex: number): void {
|
||||
var start = child.getStart(sourceFile);
|
||||
|
||||
while (currentTokenInfo.token && start >= currentTokenInfo.token.end) {
|
||||
// we've walked past the current token
|
||||
// ask parent to handle it
|
||||
currentTokenInfo = consumeCurrentToken(node, childContextNode, indentation);
|
||||
childContextNode = node;
|
||||
}
|
||||
|
||||
if (!currentTokenInfo.token) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure that current token is inside child node
|
||||
Debug.assert(currentTokenInfo.token.end <= child.end);
|
||||
if (isToken(child) && currentTokenInfo.token.end === child.end) {
|
||||
// tokens belong to parent nodes
|
||||
currentTokenInfo = consumeCurrentToken(node, childContextNode, indentation);
|
||||
childContextNode = node;
|
||||
}
|
||||
else {
|
||||
var childStartLine = getNonAdjustedLineAndCharacterFromPosition(start, sourceFile).line;
|
||||
|
||||
var childIndentation = indentation;
|
||||
if (listElementIndex === -1) {
|
||||
// child is not list element
|
||||
|
||||
}
|
||||
else {
|
||||
// child is a list element
|
||||
}
|
||||
// determine child indentation
|
||||
// if child
|
||||
// TODO: share this code with SmartIndenter
|
||||
var increaseIndentation =
|
||||
childStartLine !== nodeStartLine &&
|
||||
!SmartIndenter.childStartsOnTheSameLineWithElseInIfStatement(node, child, childStartLine, sourceFile) &&
|
||||
SmartIndenter.nodeContentIsIndented(node, child);
|
||||
|
||||
processNode(child, childContextNode, childStartLine, increaseIndentation ? indentation + options.IndentSize : indentation);
|
||||
childContextNode = node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fetchNextTokenInfo(): TokenInfo {
|
||||
if (currentTokenInfo) {
|
||||
lastTriviaWasNewLine =
|
||||
currentTokenInfo.trailingTrivia &&
|
||||
currentTokenInfo.trailingTrivia[currentTokenInfo.trailingTrivia.length - 1].kind === SyntaxKind.NewLineTrivia;
|
||||
}
|
||||
|
||||
var leadingTrivia: TextRangeWithKind[];
|
||||
var trailingTrivia: TextRangeWithKind[];
|
||||
var tokenRange: TextRangeWithKind;
|
||||
|
||||
var startPos = scanner.getStartPos();
|
||||
var initialStartPos = startPos;
|
||||
|
||||
while (startPos < originalRange.end) {
|
||||
var t = scanner.getToken();
|
||||
|
||||
if (tokenRange && !isTrivia(t)) {
|
||||
// have already seen the token and item under cursor is not a trivia
|
||||
break;
|
||||
}
|
||||
|
||||
// advance the cursor
|
||||
scanner.scan();
|
||||
|
||||
var item = { pos: startPos, end: scanner.getStartPos(), kind: t };
|
||||
startPos = item.end;
|
||||
|
||||
if (isTrivia(t)) {
|
||||
if (tokenRange) {
|
||||
|
||||
if (!trailingTrivia) {
|
||||
trailingTrivia = [];
|
||||
}
|
||||
|
||||
trailingTrivia.push(item);
|
||||
|
||||
if (t === SyntaxKind.NewLineTrivia) {
|
||||
// trailing trivia is cut at the new line
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!leadingTrivia) {
|
||||
leadingTrivia = [];
|
||||
}
|
||||
|
||||
leadingTrivia.push(item);
|
||||
}
|
||||
}
|
||||
else {
|
||||
tokenRange = item;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
leadingTrivia: leadingTrivia,
|
||||
token: tokenRange,
|
||||
trailingTrivia: trailingTrivia,
|
||||
pos: initialStartPos,
|
||||
end: scanner.getStartPos()
|
||||
};
|
||||
}
|
||||
|
||||
function consumeCurrentToken(parent: Node, contextNode: Node, indentation: number): TokenInfo {
|
||||
Debug.assert(rangeContainsRange(parent, currentTokenInfo.token));
|
||||
|
||||
if (currentTokenInfo.leadingTrivia) {
|
||||
processTrivia(currentTokenInfo.leadingTrivia, parent, contextNode, indentation);
|
||||
}
|
||||
|
||||
processRange(currentTokenInfo.token, parent, contextNode, indentation);
|
||||
|
||||
if (currentTokenInfo.trailingTrivia) {
|
||||
processTrivia(currentTokenInfo.trailingTrivia, parent, contextNode, indentation);
|
||||
}
|
||||
|
||||
return fetchNextTokenInfo();
|
||||
}
|
||||
|
||||
function processTrivia(trivia: TextRangeWithKind[], parent: Node, contextNode: Node, currentIndentation: number): void {
|
||||
for (var i = 0, len = trivia.length; i < len; ++i) {
|
||||
var triviaItem = trivia[i];
|
||||
if (isComment(triviaItem.kind) && rangeContainsRange(originalRange, triviaItem)) {
|
||||
processRange(triviaItem, parent, contextNode, currentIndentation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processRange(range: TextRangeWithKind, parent: Node, contextNode: Node, indentation: number) {
|
||||
var rangeStart = getNonAdjustedLineAndCharacterFromPosition(range.pos, sourceFile);
|
||||
if (rangeContainsRange(originalRange, range)) {
|
||||
var indentToken = false;
|
||||
if (!previousRange) {
|
||||
var originalStart = getNonAdjustedLineAndCharacterFromPosition(originalRange.pos, sourceFile);
|
||||
// TODO: implement
|
||||
if (isTrivia(range.kind)) {
|
||||
trimTrailingWhitespacesForLines(originalStart.line, rangeStart.line);
|
||||
}
|
||||
else {
|
||||
trimTrailingWhitespacesForLines(originalStart.line, rangeStart.line);
|
||||
}
|
||||
}
|
||||
else {
|
||||
processPair(range, rangeStart.line, parent, previousRange, previousRangeStartLine, previousParent, contextNode)
|
||||
indentToken = rangeStart.line !== previousRangeStartLine;
|
||||
}
|
||||
|
||||
if (lastTriviaWasNewLine && indentToken) {
|
||||
// TODO: handle indentation in multiline comments
|
||||
if (!isTrivia(range.kind)) {
|
||||
var currentIndentation = rangeStart.character;
|
||||
if (indentation !== currentIndentation) {
|
||||
var indentationString = getIndentationString(indentation, options);
|
||||
var startLinePosition = getStartPositionOfLine(rangeStart.line, sourceFile);
|
||||
recordReplace(startLinePosition, currentIndentation, indentationString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
previousRange = range;
|
||||
previousParent = parent;
|
||||
previousRangeStartLine = rangeStart.line;
|
||||
}
|
||||
|
||||
function processPair(currentItem: TextRangeWithKind,
|
||||
currentStartLine: number,
|
||||
currentParent: Node,
|
||||
previousItem: TextRangeWithKind,
|
||||
previousStartLine: number,
|
||||
previousParent: Node,
|
||||
contextNode: Node): void {
|
||||
|
||||
// TODO: compute common parent
|
||||
formattingContext.updateContext(previousItem, previousParent, currentItem, currentParent, contextNode);
|
||||
var rule = rulesProvider.getRulesMap().GetRule(formattingContext);
|
||||
|
||||
var trimTrailingWhitespaces: boolean;
|
||||
if (rule) {
|
||||
applyRuleEdits(rule, previousItem, previousStartLine, currentItem, currentStartLine);
|
||||
|
||||
if (rule.Operation.Action & (RuleAction.Space | RuleAction.Delete) && currentStartLine !== previousStartLine) {
|
||||
// Old code:
|
||||
// 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.
|
||||
// this.forceSkipIndentingNextToken(t2.start());
|
||||
lastTriviaWasNewLine = false;
|
||||
}
|
||||
else if (rule.Operation.Action & RuleAction.NewLine && currentStartLine === previousStartLine) {
|
||||
// Old code:
|
||||
// Handle the case where token2 is moved to the new line.
|
||||
// In this case we indent token2 in the next pass but we set
|
||||
// sameLineIndent flag to notify the indenter that the indentation is within the line.
|
||||
// this.forceIndentNextToken(t2.start());
|
||||
lastTriviaWasNewLine = true;
|
||||
}
|
||||
|
||||
// TODO: check if this is still needed
|
||||
trimTrailingWhitespaces =
|
||||
(rule.Operation.Action & (RuleAction.NewLine | RuleAction.Space)) &&
|
||||
rule.Flag !== RuleFlags.CanDeleteNewLines;
|
||||
}
|
||||
else {
|
||||
trimTrailingWhitespaces = true;
|
||||
}
|
||||
|
||||
if (currentStartLine !== previousStartLine && trimTrailingWhitespaces) {
|
||||
// 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
|
||||
trimTrailingWhitespacesForLines(previousStartLine, currentStartLine, previousItem);
|
||||
}
|
||||
}
|
||||
|
||||
function trimTrailingWhitespacesForLines(line1: number, line2: number, range?: TextRangeWithKind) {
|
||||
for (var line = line1; line < line2; ++line) {
|
||||
var lineStartPosition = getStartPositionOfLine(line, sourceFile);
|
||||
var lineEndPosition = getEndLinePosition(line, sourceFile);
|
||||
|
||||
// if (token && (token.kind == SyntaxKind.MultiLineCommentTrivia || token.kind == SyntaxKind.SingleLineCommentTrivia) && token.start() <= line.endPosition() && token.end() >= line.endPosition())
|
||||
|
||||
if (range && isComment(range.kind)&& false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var pos = lineEndPosition;
|
||||
while (pos >= lineStartPosition && isWhiteSpace(sourceFile.text.charCodeAt(pos))) {
|
||||
pos--;
|
||||
}
|
||||
if (pos !== lineEndPosition) {
|
||||
Debug.assert(pos === lineStartPosition || !isWhiteSpace(sourceFile.text.charCodeAt(pos)));
|
||||
recordDelete(pos + 1, lineEndPosition - pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function newTextChange(start: number, len: number, newText: string): TextChange {
|
||||
return { span: new TypeScript.TextSpan(start, len), newText: newText }
|
||||
}
|
||||
|
||||
function recordDelete(start: number, len: number) {
|
||||
if (len) {
|
||||
edits.push(newTextChange(start, len, ""));
|
||||
}
|
||||
}
|
||||
|
||||
function recordReplace(start: number, len: number, newText: string) {
|
||||
if (len || newText) {
|
||||
edits.push(newTextChange(start, len, newText));
|
||||
}
|
||||
}
|
||||
|
||||
function applyRuleEdits(rule: Rule,
|
||||
previousRange: TextRangeWithKind,
|
||||
previousStartLine: number,
|
||||
currentRange: TextRangeWithKind,
|
||||
currentStartLine: number): void {
|
||||
|
||||
var between: TextRange;
|
||||
switch (rule.Operation.Action) {
|
||||
case RuleAction.Ignore:
|
||||
// no action required
|
||||
return;
|
||||
case RuleAction.Delete:
|
||||
if (previousRange.end !== currentRange.pos) {
|
||||
// delete characters starting from t1.end up to t2.pos exclusive
|
||||
recordDelete(previousRange.end, currentRange.pos - previousRange.end);
|
||||
}
|
||||
break;
|
||||
case RuleAction.NewLine:
|
||||
// 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) {
|
||||
return;
|
||||
}
|
||||
|
||||
// edit should not be applied only if we have one line feed between elements
|
||||
var lineDelta = currentStartLine - previousStartLine;
|
||||
if (lineDelta !== 1) {
|
||||
recordReplace(previousRange.end, currentRange.pos - previousRange.end, options.NewLineCharacter);
|
||||
}
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
var posDelta = currentRange.pos - previousRange.end;
|
||||
if (posDelta !== 1 || sourceFile.text.charCodeAt(previousRange.end) !== CharacterCodes.space) {
|
||||
recordReplace(previousRange.end, currentRange.pos - previousRange.end, " ");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
320
src/services/formatting/new/formatter.ts
Normal file
320
src/services/formatting/new/formatter.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
module TypeScript.Services.Formatting {
|
||||
export class Formatter extends MultipleTokenIndenter {
|
||||
private previousTokenSpan: TokenSpan = null;
|
||||
private previousTokenParent: IndentationNodeContext = null;
|
||||
|
||||
// TODO: implement it with skipped tokens in Fidelity
|
||||
private scriptHasErrors: boolean = false;
|
||||
|
||||
private rulesProvider: RulesProvider;
|
||||
private formattingRequestKind: FormattingRequestKind;
|
||||
private formattingContext: FormattingContext;
|
||||
|
||||
constructor(textSpan: TextSpan,
|
||||
sourceUnit: SourceUnitSyntax,
|
||||
indentFirstToken: boolean,
|
||||
options: FormattingOptions,
|
||||
snapshot: ITextSnapshot,
|
||||
rulesProvider: RulesProvider,
|
||||
formattingRequestKind: FormattingRequestKind) {
|
||||
|
||||
super(textSpan, sourceUnit, snapshot, indentFirstToken, options);
|
||||
|
||||
this.previousTokenParent = this.parent().clone(this.indentationNodeContextPool());
|
||||
|
||||
this.rulesProvider = rulesProvider;
|
||||
this.formattingRequestKind = formattingRequestKind;
|
||||
this.formattingContext = new FormattingContext(this.snapshot(), this.formattingRequestKind);
|
||||
}
|
||||
|
||||
public static getEdits(textSpan: TextSpan,
|
||||
sourceUnit: SourceUnitSyntax,
|
||||
options: FormattingOptions,
|
||||
indentFirstToken: boolean,
|
||||
snapshot: ITextSnapshot,
|
||||
rulesProvider: RulesProvider,
|
||||
formattingRequestKind: FormattingRequestKind): TextEditInfo[] {
|
||||
var walker = new Formatter(textSpan, sourceUnit, indentFirstToken, options, snapshot, rulesProvider, formattingRequestKind);
|
||||
visitNodeOrToken(walker, sourceUnit);
|
||||
return walker.edits();
|
||||
}
|
||||
|
||||
public visitTokenInSpan(token: ISyntaxToken): void {
|
||||
if (token.fullWidth() !== 0) {
|
||||
var tokenSpan = new TextSpan(this.position() + token.leadingTriviaWidth(), width(token));
|
||||
if (this.textSpan().containsTextSpan(tokenSpan)) {
|
||||
this.processToken(token);
|
||||
}
|
||||
}
|
||||
|
||||
// Call the base class to process the token and indent it if needed
|
||||
super.visitTokenInSpan(token);
|
||||
}
|
||||
|
||||
private processToken(token: ISyntaxToken): void {
|
||||
var position = this.position();
|
||||
|
||||
// Extract any leading comments
|
||||
if (token.leadingTriviaWidth() !== 0) {
|
||||
this.processTrivia(token.leadingTrivia(), position);
|
||||
position += token.leadingTriviaWidth();
|
||||
}
|
||||
|
||||
// Push the token
|
||||
var currentTokenSpan = new TokenSpan(token.kind(), position, width(token));
|
||||
if (!this.parent().hasSkippedOrMissingTokenChild()) {
|
||||
if (this.previousTokenSpan) {
|
||||
// Note that formatPair calls TrimWhitespaceInLineRange in between the 2 tokens
|
||||
this.formatPair(this.previousTokenSpan, this.previousTokenParent, currentTokenSpan, this.parent());
|
||||
}
|
||||
else {
|
||||
// We still want to trim whitespace even if it is the first trivia of the first token. Trim from the beginning of the span to the trivia
|
||||
this.trimWhitespaceInLineRange(this.getLineNumber(this.textSpan()), this.getLineNumber(currentTokenSpan));
|
||||
}
|
||||
}
|
||||
this.previousTokenSpan = currentTokenSpan;
|
||||
if (this.previousTokenParent) {
|
||||
// Make sure to clear the previous parent before assigning a new value to it
|
||||
this.indentationNodeContextPool().releaseNode(this.previousTokenParent, /* recursive */true);
|
||||
}
|
||||
this.previousTokenParent = this.parent().clone(this.indentationNodeContextPool());
|
||||
position += width(token);
|
||||
|
||||
// Extract any trailing comments
|
||||
if (token.trailingTriviaWidth() !== 0) {
|
||||
this.processTrivia(token.trailingTrivia(), position);
|
||||
}
|
||||
}
|
||||
|
||||
private processTrivia(triviaList: ISyntaxTriviaList, fullStart: number) {
|
||||
var position = fullStart;
|
||||
|
||||
for (var i = 0, n = triviaList.count(); i < n ; i++) {
|
||||
var trivia = triviaList.syntaxTriviaAt(i);
|
||||
// For a comment, format it like it is a token. For skipped text, eat it up as a token, but skip the formatting
|
||||
if (trivia.isComment() || trivia.isSkippedToken()) {
|
||||
var currentTokenSpan = new TokenSpan(trivia.kind(), position, trivia.fullWidth());
|
||||
if (this.textSpan().containsTextSpan(currentTokenSpan)) {
|
||||
if (trivia.isComment() && this.previousTokenSpan) {
|
||||
// Note that formatPair calls TrimWhitespaceInLineRange in between the 2 tokens
|
||||
this.formatPair(this.previousTokenSpan, this.previousTokenParent, currentTokenSpan, this.parent());
|
||||
}
|
||||
else {
|
||||
// We still want to trim whitespace even if it is the first trivia of the first token. Trim from the beginning of the span to the trivia
|
||||
var startLine = this.getLineNumber(this.previousTokenSpan || this.textSpan());
|
||||
this.trimWhitespaceInLineRange(startLine, this.getLineNumber(currentTokenSpan));
|
||||
}
|
||||
this.previousTokenSpan = currentTokenSpan;
|
||||
if (this.previousTokenParent) {
|
||||
// Make sure to clear the previous parent before assigning a new value to it
|
||||
this.indentationNodeContextPool().releaseNode(this.previousTokenParent, /* recursive */true);
|
||||
}
|
||||
this.previousTokenParent = this.parent().clone(this.indentationNodeContextPool());
|
||||
}
|
||||
}
|
||||
|
||||
position += trivia.fullWidth();
|
||||
}
|
||||
}
|
||||
|
||||
private findCommonParents(parent1: IndentationNodeContext, parent2: IndentationNodeContext): IndentationNodeContext {
|
||||
// TODO: disable debug assert message
|
||||
|
||||
var shallowParent: IndentationNodeContext;
|
||||
var shallowParentDepth: number;
|
||||
var deepParent: IndentationNodeContext;
|
||||
var deepParentDepth: number;
|
||||
|
||||
if (parent1.depth() < parent2.depth()) {
|
||||
shallowParent = parent1;
|
||||
shallowParentDepth = parent1.depth();
|
||||
deepParent = parent2;
|
||||
deepParentDepth = parent2.depth();
|
||||
}
|
||||
else {
|
||||
shallowParent = parent2;
|
||||
shallowParentDepth = parent2.depth();
|
||||
deepParent = parent1;
|
||||
deepParentDepth = parent1.depth();
|
||||
}
|
||||
|
||||
Debug.assert(shallowParentDepth >= 0, "Expected shallowParentDepth >= 0");
|
||||
Debug.assert(deepParentDepth >= 0, "Expected deepParentDepth >= 0");
|
||||
Debug.assert(deepParentDepth >= shallowParentDepth, "Expected deepParentDepth >= shallowParentDepth");
|
||||
|
||||
while (deepParentDepth > shallowParentDepth) {
|
||||
deepParent = <IndentationNodeContext>deepParent.parent();
|
||||
deepParentDepth--;
|
||||
}
|
||||
|
||||
Debug.assert(deepParentDepth === shallowParentDepth, "Expected deepParentDepth === shallowParentDepth");
|
||||
|
||||
while (deepParent.node() && shallowParent.node()) {
|
||||
if (deepParent.node() === shallowParent.node()) {
|
||||
return deepParent;
|
||||
}
|
||||
deepParent = <IndentationNodeContext>deepParent.parent();
|
||||
shallowParent = <IndentationNodeContext>shallowParent.parent();
|
||||
}
|
||||
|
||||
// The root should be the first element in the parent chain, we can not be here unless something wrong
|
||||
// happened along the way
|
||||
throw Errors.invalidOperation();
|
||||
}
|
||||
|
||||
private formatPair(t1: TokenSpan, t1Parent: IndentationNodeContext, t2: TokenSpan, t2Parent: IndentationNodeContext): void {
|
||||
var token1Line = this.getLineNumber(t1);
|
||||
var token2Line = this.getLineNumber(t2);
|
||||
|
||||
// Find common parent
|
||||
var commonParent= this.findCommonParents(t1Parent, t2Parent);
|
||||
|
||||
// Update the context
|
||||
this.formattingContext.updateContext(t1, t1Parent, t2, t2Parent, commonParent);
|
||||
|
||||
// Find rules matching the current context
|
||||
var rule = this.rulesProvider.getRulesMap().GetRule(this.formattingContext);
|
||||
|
||||
if (rule != null) {
|
||||
// Record edits from the rule
|
||||
this.RecordRuleEdits(rule, t1, t2);
|
||||
|
||||
// Handle the case where the next line is moved to be the end of this line.
|
||||
// In this case we don't indent the next line in the next pass.
|
||||
if ((rule.Operation.Action == RuleAction.Space || rule.Operation.Action == RuleAction.Delete) &&
|
||||
token1Line != token2Line) {
|
||||
this.forceSkipIndentingNextToken(t2.start());
|
||||
}
|
||||
|
||||
// Handle the case where token2 is moved to the new line.
|
||||
// In this case we indent token2 in the next pass but we set
|
||||
// sameLineIndent flag to notify the indenter that the indentation is within the line.
|
||||
if (rule.Operation.Action == RuleAction.NewLine && token1Line == token2Line) {
|
||||
this.forceIndentNextToken(t2.start());
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
if (token1Line != token2Line && (!rule || (rule.Operation.Action != RuleAction.Delete && rule.Flag != RuleFlags.CanDeleteNewLines))) {
|
||||
this.trimWhitespaceInLineRange(token1Line, token2Line, t1);
|
||||
}
|
||||
}
|
||||
|
||||
private getLineNumber(span: TextSpan): number {
|
||||
return this.snapshot().getLineNumberFromPosition(span.start());
|
||||
}
|
||||
|
||||
private trimWhitespaceInLineRange(startLine: number, endLine: number, token?: TokenSpan): void {
|
||||
for (var lineNumber = startLine; lineNumber < endLine; ++lineNumber) {
|
||||
var line = this.snapshot().getLineFromLineNumber(lineNumber);
|
||||
|
||||
this.trimWhitespace(line, token);
|
||||
}
|
||||
}
|
||||
|
||||
private trimWhitespace(line: ITextSnapshotLine, token?: TokenSpan): void {
|
||||
// Don't remove the trailing spaces inside comments (this includes line comments and block comments)
|
||||
if (token && (token.kind == SyntaxKind.MultiLineCommentTrivia || token.kind == SyntaxKind.SingleLineCommentTrivia) && token.start() <= line.endPosition() && token.end() >= line.endPosition())
|
||||
return;
|
||||
|
||||
var text = line.getText();
|
||||
var index = 0;
|
||||
|
||||
for (index = text.length - 1; index >= 0; --index) {
|
||||
if (!CharacterInfo.isWhitespace(text.charCodeAt(index))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
++index;
|
||||
|
||||
if (index < text.length) {
|
||||
this.recordEdit(line.startPosition() + index, line.length() - index, "");
|
||||
}
|
||||
}
|
||||
|
||||
private RecordRuleEdits(rule: Rule, t1: TokenSpan, t2: TokenSpan): void {
|
||||
if (rule.Operation.Action == RuleAction.Ignore) {
|
||||
return;
|
||||
}
|
||||
|
||||
var betweenSpan: TextSpan;
|
||||
|
||||
switch (rule.Operation.Action) {
|
||||
case RuleAction.Delete:
|
||||
{
|
||||
betweenSpan = new TextSpan(t1.end(), t2.start() - t1.end());
|
||||
|
||||
if (betweenSpan.length() > 0) {
|
||||
this.recordEdit(betweenSpan.start(), betweenSpan.length(), "");
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RuleAction.NewLine:
|
||||
{
|
||||
if (!(rule.Flag == RuleFlags.CanDeleteNewLines || this.getLineNumber(t1) == this.getLineNumber(t2))) {
|
||||
return;
|
||||
}
|
||||
|
||||
betweenSpan = new TextSpan(t1.end(), t2.start() - t1.end());
|
||||
|
||||
var doEdit = false;
|
||||
var betweenText = this.snapshot().getText(betweenSpan);
|
||||
|
||||
var lineFeedLoc = betweenText.indexOf(this.options.newLineCharacter);
|
||||
if (lineFeedLoc < 0) {
|
||||
// no linefeeds, do the edit
|
||||
doEdit = true;
|
||||
}
|
||||
else {
|
||||
// We only require one line feed. If there is another one, do the edit
|
||||
lineFeedLoc = betweenText.indexOf(this.options.newLineCharacter, lineFeedLoc + 1);
|
||||
if (lineFeedLoc >= 0) {
|
||||
doEdit = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (doEdit) {
|
||||
this.recordEdit(betweenSpan.start(), betweenSpan.length(), this.options.newLineCharacter);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RuleAction.Space:
|
||||
{
|
||||
if (!(rule.Flag == RuleFlags.CanDeleteNewLines || this.getLineNumber(t1) == this.getLineNumber(t2))) {
|
||||
return;
|
||||
}
|
||||
|
||||
betweenSpan = new TextSpan(t1.end(), t2.start() - t1.end());
|
||||
|
||||
if (betweenSpan.length() > 1 || this.snapshot().getText(betweenSpan) != " ") {
|
||||
this.recordEdit(betweenSpan.start(), betweenSpan.length(), " ");
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/services/formatting/new/formatting.ts
Normal file
39
src/services/formatting/new/formatting.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='..\..\services.ts' />
|
||||
///<reference path='textSnapshot.ts' />
|
||||
///<reference path='textSnapshotLine.ts' />
|
||||
///<reference path='snapshotPoint.ts' />
|
||||
///<reference path='formattingContext.ts' />
|
||||
// ///<reference path='formattingManager.ts' />
|
||||
///<reference path='formattingRequestKind.ts' />
|
||||
///<reference path='rule.ts' />
|
||||
///<reference path='ruleAction.ts' />
|
||||
///<reference path='ruleDescriptor.ts' />
|
||||
///<reference path='ruleFlag.ts' />
|
||||
///<reference path='ruleOperation.ts' />
|
||||
///<reference path='ruleOperationContext.ts' />
|
||||
///<reference path='rules.ts' />
|
||||
///<reference path='rulesMap.ts' />
|
||||
///<reference path='rulesProvider.ts' />
|
||||
///<reference path='textEditInfo.ts' />
|
||||
///<reference path='tokenRange.ts' />
|
||||
///<reference path='tokenSpan.ts' />
|
||||
///<reference path='indentationNodeContext.ts' />
|
||||
///<reference path='indentationNodeContextPool.ts' />
|
||||
// ///<reference path='indentationTrackingWalker.ts' />
|
||||
// ///<reference path='multipleTokenIndenter.ts' />
|
||||
// ///<reference path='formatter.ts' />
|
||||
130
src/services/formatting/new/formattingContext.ts
Normal file
130
src/services/formatting/new/formattingContext.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
/// <reference path="formatting.ts"/>
|
||||
|
||||
module ts.formatting {
|
||||
export class FormattingContext {
|
||||
public currentTokenSpan: TextRangeWithKind = null;
|
||||
public nextTokenSpan: TextRangeWithKind = null;
|
||||
public contextNode: Node = null;
|
||||
public currentTokenParent: Node = null;
|
||||
public nextTokenParent: Node = null;
|
||||
|
||||
private contextNodeAllOnSameLine: boolean = null;
|
||||
private nextNodeAllOnSameLine: boolean = null;
|
||||
private tokensAreOnSameLine: boolean = null;
|
||||
private contextNodeBlockIsOnOneLine: boolean = null;
|
||||
private nextNodeBlockIsOnOneLine: boolean = null;
|
||||
|
||||
constructor(private sourceFile: SourceFile, public formattingRequestKind: FormattingRequestKind) {
|
||||
}
|
||||
|
||||
public updateContext(currentRange: TextRangeWithKind, currentTokenParent: Node, nextRange: TextRangeWithKind, nextTokenParent: Node, commonParent: Node) {
|
||||
Debug.assert(currentRange != null, "currentTokenSpan is null");
|
||||
Debug.assert(currentTokenParent != null, "currentTokenParent is null");
|
||||
Debug.assert(nextRange != null, "nextTokenSpan is null");
|
||||
Debug.assert(nextTokenParent != null, "nextTokenParent is null");
|
||||
Debug.assert(commonParent != null, "commonParent is null");
|
||||
|
||||
this.currentTokenSpan = currentRange;
|
||||
this.currentTokenParent = currentTokenParent;
|
||||
this.nextTokenSpan = nextRange;
|
||||
this.nextTokenParent = nextTokenParent;
|
||||
this.contextNode = commonParent;
|
||||
|
||||
this.contextNodeAllOnSameLine = null;
|
||||
this.nextNodeAllOnSameLine = null;
|
||||
this.tokensAreOnSameLine = null;
|
||||
this.contextNodeBlockIsOnOneLine = null;
|
||||
this.nextNodeBlockIsOnOneLine = null;
|
||||
}
|
||||
|
||||
public ContextNodeAllOnSameLine(): boolean {
|
||||
if (this.contextNodeAllOnSameLine === null) {
|
||||
this.contextNodeAllOnSameLine = this.NodeIsOnOneLine(this.contextNode);
|
||||
}
|
||||
|
||||
return this.contextNodeAllOnSameLine;
|
||||
}
|
||||
|
||||
public NextNodeAllOnSameLine(): boolean {
|
||||
if (this.nextNodeAllOnSameLine === null) {
|
||||
this.nextNodeAllOnSameLine = this.NodeIsOnOneLine(this.nextTokenParent);
|
||||
}
|
||||
|
||||
return this.nextNodeAllOnSameLine;
|
||||
}
|
||||
|
||||
public TokensAreOnSameLine(): boolean {
|
||||
if (this.tokensAreOnSameLine === null) {
|
||||
|
||||
//var startLine = this.snapshot.getLineNumberFromPosition(this.currentTokenSpan.token.pos);
|
||||
//var endLine = this.snapshot.getLineNumberFromPosition(this.nextTokenSpan.token.pos);
|
||||
|
||||
var startLine = this.sourceFile.getLineAndCharacterFromPosition(this.currentTokenSpan.pos).line;
|
||||
var endLine = this.sourceFile.getLineAndCharacterFromPosition(this.nextTokenSpan.pos).line;
|
||||
this.tokensAreOnSameLine = (startLine == endLine);
|
||||
}
|
||||
|
||||
return this.tokensAreOnSameLine;
|
||||
}
|
||||
|
||||
public ContextNodeBlockIsOnOneLine() {
|
||||
if (this.contextNodeBlockIsOnOneLine === null) {
|
||||
this.contextNodeBlockIsOnOneLine = this.BlockIsOnOneLine(this.contextNode);
|
||||
}
|
||||
|
||||
return this.contextNodeBlockIsOnOneLine;
|
||||
}
|
||||
|
||||
public NextNodeBlockIsOnOneLine() {
|
||||
if (this.nextNodeBlockIsOnOneLine === null) {
|
||||
this.nextNodeBlockIsOnOneLine = this.BlockIsOnOneLine(this.nextTokenParent);
|
||||
}
|
||||
|
||||
return this.nextNodeBlockIsOnOneLine;
|
||||
}
|
||||
|
||||
public NodeIsOnOneLine(node: Node): boolean {
|
||||
return;
|
||||
|
||||
var startLine = this.sourceFile.getLineAndCharacterFromPosition(node.getStart(this.sourceFile)).line;
|
||||
var endLine = this.sourceFile.getLineAndCharacterFromPosition(node.getEnd()).line;
|
||||
//var startLine = this.snapshot.getLineNumberFromPosition(node.start());
|
||||
//var endLine = this.snapshot.getLineNumberFromPosition(node.end());
|
||||
|
||||
return startLine == endLine;
|
||||
}
|
||||
|
||||
// Now we know we have a block (or a fake block represented by some other kind of node with an open and close brace as children).
|
||||
// IMPORTANT!!! This relies on the invariant that IsBlockContext must return true ONLY for nodes with open and close braces as immediate children
|
||||
public BlockIsOnOneLine(node: Node): boolean {
|
||||
var openBrace = findChildOfKind(node, SyntaxKind.OpenBraceToken, this.sourceFile);
|
||||
var closeBrace = findChildOfKind(node, SyntaxKind.CloseBraceToken, this.sourceFile);
|
||||
if (openBrace && closeBrace) {
|
||||
var startLine = this.sourceFile.getLineAndCharacterFromPosition(openBrace.getEnd()).line;
|
||||
var endLine = this.sourceFile.getLineAndCharacterFromPosition(closeBrace.getStart(this.sourceFile)).line;
|
||||
return startLine === endLine;
|
||||
}
|
||||
|
||||
//var block = <BlockSyntax>node.node();
|
||||
|
||||
//// Now check if they are on the same line
|
||||
//return this.snapshot.getLineNumberFromPosition(end(block.openBraceToken)) ===
|
||||
// this.snapshot.getLineNumberFromPosition(start(block.closeBraceToken));
|
||||
}
|
||||
}
|
||||
}
|
||||
123
src/services/formatting/new/formattingManager.ts
Normal file
123
src/services/formatting/new/formattingManager.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
/// <reference path="formatting.ts"/>
|
||||
|
||||
module TypeScript.Services.Formatting {
|
||||
export class FormattingManager {
|
||||
private options: FormattingOptions;
|
||||
|
||||
constructor(private syntaxTree: SyntaxTree,
|
||||
private snapshot: ITextSnapshot,
|
||||
private rulesProvider: RulesProvider,
|
||||
editorOptions: ts.EditorOptions) {
|
||||
//
|
||||
// TODO: convert to use FormattingOptions instead of EditorOptions
|
||||
this.options = new FormattingOptions(!editorOptions.ConvertTabsToSpaces, editorOptions.TabSize, editorOptions.IndentSize, editorOptions.NewLineCharacter)
|
||||
}
|
||||
|
||||
public formatSelection(minChar: number, limChar: number): ts.TextChange[] {
|
||||
var span = TextSpan.fromBounds(minChar, limChar);
|
||||
return this.formatSpan(span, FormattingRequestKind.FormatSelection);
|
||||
}
|
||||
|
||||
public formatDocument(): ts.TextChange[] {
|
||||
var span = TextSpan.fromBounds(0, this.snapshot.getLength());
|
||||
return this.formatSpan(span, FormattingRequestKind.FormatDocument);
|
||||
}
|
||||
|
||||
public formatOnSemicolon(caretPosition: number): ts.TextChange[] {
|
||||
var sourceUnit = this.syntaxTree.sourceUnit();
|
||||
var semicolonPositionedToken = findToken(sourceUnit, caretPosition - 1);
|
||||
|
||||
if (semicolonPositionedToken.kind() === SyntaxKind.SemicolonToken) {
|
||||
// Find the outer most parent that this semicolon terminates
|
||||
var current: ISyntaxElement = semicolonPositionedToken;
|
||||
while (current.parent !== null &&
|
||||
end(current.parent) === end(semicolonPositionedToken) &&
|
||||
current.parent.kind() !== SyntaxKind.List) {
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
// Compute the span
|
||||
var span = new TextSpan(fullStart(current), fullWidth(current));
|
||||
|
||||
// Format the span
|
||||
return this.formatSpan(span, FormattingRequestKind.FormatOnSemicolon);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public formatOnClosingCurlyBrace(caretPosition: number): ts.TextChange[] {
|
||||
var sourceUnit = this.syntaxTree.sourceUnit();
|
||||
var closeBracePositionedToken = findToken(sourceUnit, caretPosition - 1);
|
||||
|
||||
if (closeBracePositionedToken.kind() === SyntaxKind.CloseBraceToken) {
|
||||
// Find the outer most parent that this closing brace terminates
|
||||
var current: ISyntaxElement = closeBracePositionedToken;
|
||||
while (current.parent !== null &&
|
||||
end(current.parent) === end(closeBracePositionedToken) &&
|
||||
current.parent.kind() !== SyntaxKind.List) {
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
// Compute the span
|
||||
var span = new TextSpan(fullStart(current), fullWidth(current));
|
||||
|
||||
// Format the span
|
||||
return this.formatSpan(span, FormattingRequestKind.FormatOnClosingCurlyBrace);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public formatOnEnter(caretPosition: number): ts.TextChange[] {
|
||||
var lineNumber = this.snapshot.getLineNumberFromPosition(caretPosition);
|
||||
|
||||
if (lineNumber > 0) {
|
||||
// Format both lines
|
||||
var prevLine = this.snapshot.getLineFromLineNumber(lineNumber - 1);
|
||||
var currentLine = this.snapshot.getLineFromLineNumber(lineNumber);
|
||||
var span = TextSpan.fromBounds(prevLine.startPosition(), currentLine.endPosition());
|
||||
|
||||
// Format the span
|
||||
return this.formatSpan(span, FormattingRequestKind.FormatOnEnter);
|
||||
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private formatSpan(span: TextSpan, formattingRequestKind: FormattingRequestKind): ts.TextChange[] {
|
||||
// Always format from the beginning of the line
|
||||
var startLine = this.snapshot.getLineFromPosition(span.start());
|
||||
span = TextSpan.fromBounds(startLine.startPosition(), span.end());
|
||||
|
||||
var result: ts.TextChange[] = [];
|
||||
|
||||
var formattingEdits = Formatter.getEdits(span, this.syntaxTree.sourceUnit(), this.options, true, this.snapshot, this.rulesProvider, formattingRequestKind);
|
||||
|
||||
//
|
||||
// TODO: Change the ILanguageService interface to return TextEditInfo (with start, and length) instead of TextEdit (with minChar and limChar)
|
||||
formattingEdits.forEach((item) => {
|
||||
var edit = new ts.TextChange(new TextSpan(item.position, item.length), item.replaceWith);
|
||||
result.push(edit);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/services/formatting/new/formattingRequestKind.ts
Normal file
26
src/services/formatting/new/formattingRequestKind.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
/// <reference path="formatting.ts"/>
|
||||
|
||||
module ts.formatting {
|
||||
export enum FormattingRequestKind {
|
||||
FormatDocument,
|
||||
FormatSelection,
|
||||
FormatOnEnter,
|
||||
FormatOnSemicolon,
|
||||
FormatOnClosingCurlyBrace
|
||||
}
|
||||
}
|
||||
103
src/services/formatting/new/indentationNodeContext.ts
Normal file
103
src/services/formatting/new/indentationNodeContext.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
module ts.formatting {
|
||||
export class IndentationNodeContext {
|
||||
private _node: Node;
|
||||
private _parent: IndentationNodeContext;
|
||||
private _fullStart: number;
|
||||
private _indentationAmount: number;
|
||||
private _childIndentationAmountDelta: number;
|
||||
private _depth: number;
|
||||
private _hasSkippedOrMissingTokenChild: boolean;
|
||||
|
||||
constructor(parent: IndentationNodeContext, node: Node, fullStart: number, indentationAmount: number, childIndentationAmountDelta: number) {
|
||||
this.update(parent, node, fullStart, indentationAmount, childIndentationAmountDelta);
|
||||
}
|
||||
|
||||
public parent(): IndentationNodeContext {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
public node(): Node {
|
||||
return this._node;
|
||||
}
|
||||
|
||||
public fullStart(): number {
|
||||
return this._fullStart;
|
||||
}
|
||||
|
||||
public fullWidth(): number {
|
||||
return this._node.getFullWidth();
|
||||
}
|
||||
|
||||
public start(): number {
|
||||
return this._node.getStart();
|
||||
}
|
||||
|
||||
public end(): number {
|
||||
return this._node.getEnd();
|
||||
}
|
||||
|
||||
public indentationAmount(): number {
|
||||
return this._indentationAmount;
|
||||
}
|
||||
|
||||
public childIndentationAmountDelta(): number {
|
||||
return this._childIndentationAmountDelta;
|
||||
}
|
||||
|
||||
public depth(): number {
|
||||
return this._depth;
|
||||
}
|
||||
|
||||
public kind(): SyntaxKind {
|
||||
return this._node.kind;
|
||||
}
|
||||
|
||||
public hasSkippedOrMissingTokenChild(): boolean {
|
||||
if (this._hasSkippedOrMissingTokenChild === null) {
|
||||
// this._hasSkippedOrMissingTokenChild = Syntax.nodeHasSkippedOrMissingTokens(this._node);
|
||||
}
|
||||
return this._hasSkippedOrMissingTokenChild;
|
||||
}
|
||||
|
||||
public clone(pool: IndentationNodeContextPool): IndentationNodeContext {
|
||||
var parent: IndentationNodeContext = null;
|
||||
if (this._parent) {
|
||||
parent = this._parent.clone(pool);
|
||||
}
|
||||
return pool.getNode(parent, this._node, this._fullStart, this._indentationAmount, this._childIndentationAmountDelta);
|
||||
}
|
||||
|
||||
public update(parent: IndentationNodeContext, node: Node, fullStart: number, indentationAmount: number, childIndentationAmountDelta: number) {
|
||||
this._parent = parent;
|
||||
this._node = node;
|
||||
this._fullStart = fullStart;
|
||||
this._indentationAmount = indentationAmount;
|
||||
this._childIndentationAmountDelta = childIndentationAmountDelta;
|
||||
this._hasSkippedOrMissingTokenChild = null;
|
||||
|
||||
if (parent) {
|
||||
this._depth = parent.depth() + 1;
|
||||
}
|
||||
else {
|
||||
this._depth = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/services/formatting/new/indentationNodeContextPool.ts
Normal file
43
src/services/formatting/new/indentationNodeContextPool.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
module ts.formatting {
|
||||
export class IndentationNodeContextPool {
|
||||
private nodes: IndentationNodeContext[] = [];
|
||||
|
||||
public getNode(parent: IndentationNodeContext, node: Node, fullStart: number, indentationLevel: number, childIndentationLevelDelta: number): IndentationNodeContext {
|
||||
if (this.nodes.length > 0) {
|
||||
var cachedNode = this.nodes.pop();
|
||||
cachedNode.update(parent, node, fullStart, indentationLevel, childIndentationLevelDelta);
|
||||
return cachedNode;
|
||||
}
|
||||
|
||||
return new IndentationNodeContext(parent, node, fullStart, indentationLevel, childIndentationLevelDelta);
|
||||
}
|
||||
|
||||
public releaseNode(node: IndentationNodeContext, recursive: boolean = false): void {
|
||||
this.nodes.push(node);
|
||||
|
||||
if (recursive) {
|
||||
var parent = node.parent();
|
||||
if (parent) {
|
||||
this.releaseNode(parent, recursive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
349
src/services/formatting/new/indentationTrackingWalker.ts
Normal file
349
src/services/formatting/new/indentationTrackingWalker.ts
Normal file
@@ -0,0 +1,349 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
module TypeScript.Services.Formatting {
|
||||
export class IndentationTrackingWalker extends SyntaxWalker {
|
||||
private _position: number = 0;
|
||||
private _parent: IndentationNodeContext = null;
|
||||
private _textSpan: TextSpan;
|
||||
private _snapshot: ITextSnapshot;
|
||||
private _lastTriviaWasNewLine: boolean;
|
||||
private _indentationNodeContextPool: IndentationNodeContextPool;
|
||||
private _text: ISimpleText;
|
||||
|
||||
constructor(textSpan: TextSpan, sourceUnit: SourceUnitSyntax, snapshot: ITextSnapshot, indentFirstToken: boolean, public options: FormattingOptions) {
|
||||
super();
|
||||
|
||||
// Create a pool object to manage context nodes while walking the tree
|
||||
this._indentationNodeContextPool = new IndentationNodeContextPool();
|
||||
|
||||
this._textSpan = textSpan;
|
||||
this._text = sourceUnit.syntaxTree.text;
|
||||
this._snapshot = snapshot;
|
||||
this._parent = this._indentationNodeContextPool.getNode(null, sourceUnit, 0, 0, 0);
|
||||
|
||||
// Is the first token in the span at the start of a new line.
|
||||
this._lastTriviaWasNewLine = indentFirstToken;
|
||||
}
|
||||
|
||||
public position(): number {
|
||||
return this._position;
|
||||
}
|
||||
|
||||
public parent(): IndentationNodeContext {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
public textSpan(): TextSpan {
|
||||
return this._textSpan;
|
||||
}
|
||||
|
||||
public snapshot(): ITextSnapshot {
|
||||
return this._snapshot;
|
||||
}
|
||||
|
||||
public indentationNodeContextPool(): IndentationNodeContextPool {
|
||||
return this._indentationNodeContextPool;
|
||||
}
|
||||
|
||||
public forceIndentNextToken(tokenStart: number): void {
|
||||
this._lastTriviaWasNewLine = true;
|
||||
this.forceRecomputeIndentationOfParent(tokenStart, true);
|
||||
}
|
||||
|
||||
public forceSkipIndentingNextToken(tokenStart: number): void {
|
||||
this._lastTriviaWasNewLine = false;
|
||||
this.forceRecomputeIndentationOfParent(tokenStart, false);
|
||||
}
|
||||
|
||||
public indentToken(token: ISyntaxToken, indentationAmount: number, commentIndentationAmount: number): void {
|
||||
throw Errors.abstract();
|
||||
}
|
||||
|
||||
public visitTokenInSpan(token: ISyntaxToken): void {
|
||||
if (this._lastTriviaWasNewLine) {
|
||||
// Compute the indentation level at the current token
|
||||
var indentationAmount = this.getTokenIndentationAmount(token);
|
||||
var commentIndentationAmount = this.getCommentIndentationAmount(token);
|
||||
|
||||
// Process the token
|
||||
this.indentToken(token, indentationAmount, commentIndentationAmount);
|
||||
}
|
||||
}
|
||||
|
||||
public visitToken(token: ISyntaxToken): void {
|
||||
var tokenSpan = new TextSpan(this._position, token.fullWidth());
|
||||
|
||||
if (tokenSpan.intersectsWithTextSpan(this._textSpan)) {
|
||||
this.visitTokenInSpan(token);
|
||||
|
||||
// Only track new lines on tokens within the range. Make sure to check that the last trivia is a newline, and not just one of the trivia
|
||||
var trivia = token.trailingTrivia();
|
||||
this._lastTriviaWasNewLine = trivia.hasNewLine() && trivia.syntaxTriviaAt(trivia.count() - 1).kind() == SyntaxKind.NewLineTrivia;
|
||||
}
|
||||
|
||||
// Update the position
|
||||
this._position += token.fullWidth();
|
||||
}
|
||||
|
||||
public visitNode(node: ISyntaxNode): void {
|
||||
var nodeSpan = new TextSpan(this._position, fullWidth(node));
|
||||
|
||||
if (nodeSpan.intersectsWithTextSpan(this._textSpan)) {
|
||||
// Update indentation level
|
||||
var indentation = this.getNodeIndentation(node);
|
||||
|
||||
// Update the parent
|
||||
var currentParent = this._parent;
|
||||
this._parent = this._indentationNodeContextPool.getNode(currentParent, node, this._position, indentation.indentationAmount, indentation.indentationAmountDelta);
|
||||
|
||||
// Visit node
|
||||
visitNodeOrToken(this, node);
|
||||
|
||||
// Reset state
|
||||
this._indentationNodeContextPool.releaseNode(this._parent);
|
||||
this._parent = currentParent;
|
||||
}
|
||||
else {
|
||||
// We're skipping the node, so update our position accordingly.
|
||||
this._position += fullWidth(node);
|
||||
}
|
||||
}
|
||||
|
||||
private getTokenIndentationAmount(token: ISyntaxToken): number {
|
||||
// If this is the first token of a node, it should follow the node indentation and not the child indentation;
|
||||
// (e.g.class in a class declaration or module in module declariotion).
|
||||
// Open and close braces should follow the indentation of thier parent as well(e.g.
|
||||
// class {
|
||||
// }
|
||||
// Also in a do-while statement, the while should be indented like the parent.
|
||||
if (firstToken(this._parent.node()) === token ||
|
||||
token.kind() === SyntaxKind.OpenBraceToken || token.kind() === SyntaxKind.CloseBraceToken ||
|
||||
token.kind() === SyntaxKind.OpenBracketToken || token.kind() === SyntaxKind.CloseBracketToken ||
|
||||
(token.kind() === SyntaxKind.WhileKeyword && this._parent.node().kind() == SyntaxKind.DoStatement)) {
|
||||
return this._parent.indentationAmount();
|
||||
}
|
||||
|
||||
return (this._parent.indentationAmount() + this._parent.childIndentationAmountDelta());
|
||||
}
|
||||
|
||||
private getCommentIndentationAmount(token: ISyntaxToken): number {
|
||||
// If this is token terminating an indentation scope, leading comments should be indented to follow the children
|
||||
// indentation level and not the node
|
||||
|
||||
if (token.kind() === SyntaxKind.CloseBraceToken || token.kind() === SyntaxKind.CloseBracketToken) {
|
||||
return (this._parent.indentationAmount() + this._parent.childIndentationAmountDelta());
|
||||
}
|
||||
return this._parent.indentationAmount();
|
||||
}
|
||||
|
||||
private getNodeIndentation(node: ISyntaxNode, newLineInsertedByFormatting?: boolean): { indentationAmount: number; indentationAmountDelta: number; } {
|
||||
var parent = this._parent;
|
||||
|
||||
// We need to get the parent's indentation, which could be one of 2 things. If first token of the parent is in the span, use the parent's computed indentation.
|
||||
// If the parent was outside the span, use the actual indentation of the parent.
|
||||
var parentIndentationAmount: number;
|
||||
if (this._textSpan.containsPosition(parent.start())) {
|
||||
parentIndentationAmount = parent.indentationAmount();
|
||||
}
|
||||
else {
|
||||
if (parent.kind() === SyntaxKind.Block && !this.shouldIndentBlockInParent(this._parent.parent())) {
|
||||
// Blocks preserve the indentation of their containing node (unless they're a
|
||||
// standalone block in a list). i.e. if you have:
|
||||
//
|
||||
// function foo(
|
||||
// a: number) {
|
||||
//
|
||||
// Then we expect the indentation of the block to be tied to the function, not to
|
||||
// the line that the block is defined on. If we were to do the latter, then the
|
||||
// indentation would be here:
|
||||
//
|
||||
// function foo(
|
||||
// a: number) {
|
||||
// |
|
||||
//
|
||||
// Instead of:
|
||||
//
|
||||
// function foo(
|
||||
// a: number) {
|
||||
// |
|
||||
parent = this._parent.parent();
|
||||
}
|
||||
|
||||
var line = this._snapshot.getLineFromPosition(parent.start()).getText();
|
||||
var firstNonWhiteSpacePosition = Indentation.firstNonWhitespacePosition(line);
|
||||
parentIndentationAmount = Indentation.columnForPositionInString(line, firstNonWhiteSpacePosition, this.options);
|
||||
}
|
||||
var parentIndentationAmountDelta = parent.childIndentationAmountDelta();
|
||||
|
||||
// The indentation level of the node
|
||||
var indentationAmount: number;
|
||||
|
||||
// The delta it adds to its children.
|
||||
var indentationAmountDelta: number;
|
||||
var parentNode = parent.node();
|
||||
|
||||
switch (node.kind()) {
|
||||
default:
|
||||
// General case
|
||||
// This node should follow the child indentation set by its parent
|
||||
// This node does not introduce any new indentation scope, indent any decendants of this node (tokens or child nodes)
|
||||
// using the same indentation level
|
||||
indentationAmount = (parentIndentationAmount + parentIndentationAmountDelta);
|
||||
indentationAmountDelta = 0;
|
||||
break;
|
||||
|
||||
// Statements introducing {}
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
case SyntaxKind.ObjectType:
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
case SyntaxKind.SwitchStatement:
|
||||
case SyntaxKind.ObjectLiteralExpression:
|
||||
case SyntaxKind.ConstructorDeclaration:
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.MemberFunctionDeclaration:
|
||||
case SyntaxKind.GetAccessor:
|
||||
case SyntaxKind.SetAccessor:
|
||||
case SyntaxKind.IndexMemberDeclaration:
|
||||
case SyntaxKind.CatchClause:
|
||||
// Statements introducing []
|
||||
case SyntaxKind.ArrayLiteralExpression:
|
||||
case SyntaxKind.ArrayType:
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
case SyntaxKind.IndexSignature:
|
||||
// Other statements
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.WhileStatement:
|
||||
case SyntaxKind.DoStatement:
|
||||
case SyntaxKind.WithStatement:
|
||||
case SyntaxKind.CaseSwitchClause:
|
||||
case SyntaxKind.DefaultSwitchClause:
|
||||
case SyntaxKind.ReturnStatement:
|
||||
case SyntaxKind.ThrowStatement:
|
||||
case SyntaxKind.SimpleArrowFunctionExpression:
|
||||
case SyntaxKind.ParenthesizedArrowFunctionExpression:
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
case SyntaxKind.ExportAssignment:
|
||||
|
||||
// Expressions which have argument lists or parameter lists
|
||||
case SyntaxKind.InvocationExpression:
|
||||
case SyntaxKind.ObjectCreationExpression:
|
||||
case SyntaxKind.CallSignature:
|
||||
case SyntaxKind.ConstructSignature:
|
||||
|
||||
// These nodes should follow the child indentation set by its parent;
|
||||
// they introduce a new indenation scope; children should be indented at one level deeper
|
||||
indentationAmount = (parentIndentationAmount + parentIndentationAmountDelta);
|
||||
indentationAmountDelta = this.options.indentSpaces;
|
||||
break;
|
||||
|
||||
case SyntaxKind.IfStatement:
|
||||
if (parent.kind() === SyntaxKind.ElseClause &&
|
||||
!SyntaxUtilities.isLastTokenOnLine((<ElseClauseSyntax>parentNode).elseKeyword, this._text)) {
|
||||
// This is an else if statement with the if on the same line as the else, do not indent the if statmement.
|
||||
// Note: Children indentation has already been set by the parent if statement, so no need to increment
|
||||
indentationAmount = parentIndentationAmount;
|
||||
}
|
||||
else {
|
||||
// Otherwise introduce a new indenation scope; children should be indented at one level deeper
|
||||
indentationAmount = (parentIndentationAmount + parentIndentationAmountDelta);
|
||||
}
|
||||
indentationAmountDelta = this.options.indentSpaces;
|
||||
break;
|
||||
|
||||
case SyntaxKind.ElseClause:
|
||||
// Else should always follow its parent if statement indentation.
|
||||
// Note: Children indentation has already been set by the parent if statement, so no need to increment
|
||||
indentationAmount = parentIndentationAmount;
|
||||
indentationAmountDelta = this.options.indentSpaces;
|
||||
break;
|
||||
|
||||
|
||||
case SyntaxKind.Block:
|
||||
// Check if the block is a member in a list of statements (if the parent is a source unit, module, or block, or switch clause)
|
||||
if (this.shouldIndentBlockInParent(parent)) {
|
||||
indentationAmount = parentIndentationAmount + parentIndentationAmountDelta;
|
||||
}
|
||||
else {
|
||||
indentationAmount = parentIndentationAmount;
|
||||
}
|
||||
|
||||
indentationAmountDelta = this.options.indentSpaces;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the parent happens to start on the same line as this node, then override the current node indenation with that
|
||||
// of the parent. This avoid having to add an extra level of indentation for the children. e.g.:
|
||||
// return {
|
||||
// a:1
|
||||
// };
|
||||
// instead of:
|
||||
// return {
|
||||
// a:1
|
||||
// };
|
||||
// We also need to pass the delta (if it is nonzero) to the children, so that subsequent lines get indented. Essentially, if any node starting on the given line
|
||||
// has a nonzero delta , the resulting delta should be inherited from this node. This is to indent cases like the following:
|
||||
// return a
|
||||
// || b;
|
||||
// Lastly, it is possible the node indentation needs to be recomputed because the formatter inserted a newline before its first token.
|
||||
// If this is the case, we know the node no longer starts on the same line as its parent (or at least we shouldn't treat it as such).
|
||||
if (parentNode) {
|
||||
if (!newLineInsertedByFormatting /*This could be false or undefined here*/) {
|
||||
var parentStartLine = this._snapshot.getLineNumberFromPosition(parent.start());
|
||||
var currentNodeStartLine = this._snapshot.getLineNumberFromPosition(this._position + leadingTriviaWidth(node));
|
||||
if (parentStartLine === currentNodeStartLine || newLineInsertedByFormatting === false /*meaning a new line was removed and we are force recomputing*/) {
|
||||
indentationAmount = parentIndentationAmount;
|
||||
indentationAmountDelta = Math.min(this.options.indentSpaces, parentIndentationAmountDelta + indentationAmountDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
indentationAmount: indentationAmount,
|
||||
indentationAmountDelta: indentationAmountDelta
|
||||
};
|
||||
}
|
||||
|
||||
private shouldIndentBlockInParent(parent: IndentationNodeContext): boolean {
|
||||
switch (parent.kind()) {
|
||||
case SyntaxKind.SourceUnit:
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
case SyntaxKind.Block:
|
||||
case SyntaxKind.CaseSwitchClause:
|
||||
case SyntaxKind.DefaultSwitchClause:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private forceRecomputeIndentationOfParent(tokenStart: number, newLineAdded: boolean /*as opposed to removed*/): void {
|
||||
var parent = this._parent;
|
||||
if (parent.fullStart() === tokenStart) {
|
||||
// Temporarily pop the parent before recomputing
|
||||
this._parent = parent.parent();
|
||||
var indentation = this.getNodeIndentation(parent.node(), /* newLineInsertedByFormatting */ newLineAdded);
|
||||
parent.update(parent.parent(), parent.node(), parent.fullStart(), indentation.indentationAmount, indentation.indentationAmountDelta);
|
||||
this._parent = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
206
src/services/formatting/new/multipleTokenIndenter.ts
Normal file
206
src/services/formatting/new/multipleTokenIndenter.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
module TypeScript.Services.Formatting {
|
||||
export class MultipleTokenIndenter extends IndentationTrackingWalker {
|
||||
private _edits: TextEditInfo[] = [];
|
||||
|
||||
constructor(textSpan: TextSpan, sourceUnit: SourceUnitSyntax, snapshot: ITextSnapshot, indentFirstToken: boolean, options: FormattingOptions) {
|
||||
super(textSpan, sourceUnit, snapshot, indentFirstToken, options);
|
||||
}
|
||||
|
||||
public indentToken(token: ISyntaxToken, indentationAmount: number, commentIndentationAmount: number): void {
|
||||
// Ignore generated tokens
|
||||
if (token.fullWidth() === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have any skipped tokens as children, do not process this node for indentation or formatting
|
||||
if (this.parent().hasSkippedOrMissingTokenChild()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Be strict, and only consider nodes that fall inside the span. This avoids indenting a multiline string
|
||||
// on enter at the end of, as the whole token was not included in the span
|
||||
var tokenSpan = new TextSpan(this.position() + token.leadingTriviaWidth(), width(token));
|
||||
if (!this.textSpan().containsTextSpan(tokenSpan)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute an indentation string for this token
|
||||
var indentationString = Indentation.indentationString(indentationAmount, this.options);
|
||||
|
||||
var commentIndentationString = Indentation.indentationString(commentIndentationAmount, this.options);
|
||||
|
||||
// Record any needed indentation edits
|
||||
this.recordIndentationEditsForToken(token, indentationString, commentIndentationString);
|
||||
}
|
||||
|
||||
public edits(): TextEditInfo[]{
|
||||
return this._edits;
|
||||
}
|
||||
|
||||
public recordEdit(position: number, length: number, replaceWith: string): void {
|
||||
this._edits.push(new TextEditInfo(position, length, replaceWith));
|
||||
}
|
||||
|
||||
private recordIndentationEditsForToken(token: ISyntaxToken, indentationString: string, commentIndentationString: string) {
|
||||
var position = this.position();
|
||||
var indentNextTokenOrTrivia = true;
|
||||
var leadingWhiteSpace = ""; // We need to track the whitespace before a multiline comment
|
||||
|
||||
// Process any leading trivia if any
|
||||
var triviaList = token.leadingTrivia();
|
||||
if (triviaList) {
|
||||
for (var i = 0, length = triviaList.count(); i < length; i++, position += trivia.fullWidth()) {
|
||||
var trivia = triviaList.syntaxTriviaAt(i);
|
||||
// Skip this trivia if it is not in the span
|
||||
if (!this.textSpan().containsTextSpan(new TextSpan(position, trivia.fullWidth()))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (trivia.kind()) {
|
||||
case SyntaxKind.MultiLineCommentTrivia:
|
||||
// We will only indent the first line of the multiline comment if we were planning to indent the next trivia. However,
|
||||
// subsequent lines will always be indented
|
||||
this.recordIndentationEditsForMultiLineComment(trivia, position, commentIndentationString, leadingWhiteSpace, !indentNextTokenOrTrivia /* already indented first line */);
|
||||
indentNextTokenOrTrivia = false;
|
||||
leadingWhiteSpace = "";
|
||||
break;
|
||||
|
||||
case SyntaxKind.SingleLineCommentTrivia:
|
||||
case SyntaxKind.SkippedTokenTrivia:
|
||||
if (indentNextTokenOrTrivia) {
|
||||
this.recordIndentationEditsForSingleLineOrSkippedText(trivia, position, commentIndentationString);
|
||||
indentNextTokenOrTrivia = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case SyntaxKind.WhitespaceTrivia:
|
||||
// If the next trivia is a comment, use the comment indentation level instead of the regular indentation level
|
||||
// If the next trivia is a newline, this whole line is just whitespace, so don't do anything (trimming will take care of it)
|
||||
var nextTrivia = length > i + 1 && triviaList.syntaxTriviaAt(i + 1);
|
||||
var whiteSpaceIndentationString = nextTrivia && nextTrivia.isComment() ? commentIndentationString : indentationString;
|
||||
if (indentNextTokenOrTrivia) {
|
||||
if (!(nextTrivia && nextTrivia.isNewLine())) {
|
||||
this.recordIndentationEditsForWhitespace(trivia, position, whiteSpaceIndentationString);
|
||||
}
|
||||
indentNextTokenOrTrivia = false;
|
||||
}
|
||||
leadingWhiteSpace += trivia.fullText();
|
||||
break;
|
||||
|
||||
case SyntaxKind.NewLineTrivia:
|
||||
// We hit a newline processing the trivia. We need to add the indentation to the
|
||||
// next line as well. Note: don't bother indenting the newline itself. This will
|
||||
// just insert ugly whitespace that most users probably will not want.
|
||||
indentNextTokenOrTrivia = true;
|
||||
leadingWhiteSpace = "";
|
||||
break;
|
||||
|
||||
default:
|
||||
throw Errors.invalidOperation();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (token.kind() !== SyntaxKind.EndOfFileToken && indentNextTokenOrTrivia) {
|
||||
// If the last trivia item was a new line, or no trivia items were encounterd record the
|
||||
// indentation edit at the token position
|
||||
if (indentationString.length > 0) {
|
||||
this.recordEdit(position, 0, indentationString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private recordIndentationEditsForSingleLineOrSkippedText(trivia: ISyntaxTrivia, fullStart: number, indentationString: string): void {
|
||||
// Record the edit
|
||||
if (indentationString.length > 0) {
|
||||
this.recordEdit(fullStart, 0, indentationString);
|
||||
}
|
||||
}
|
||||
|
||||
private recordIndentationEditsForWhitespace(trivia: ISyntaxTrivia, fullStart: number, indentationString: string): void {
|
||||
var text = trivia.fullText();
|
||||
|
||||
// Check if the current indentation matches the desired indentation or not
|
||||
if (indentationString === text) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Record the edit
|
||||
this.recordEdit(fullStart, text.length, indentationString);
|
||||
}
|
||||
|
||||
private recordIndentationEditsForMultiLineComment(trivia: ISyntaxTrivia, fullStart: number, indentationString: string, leadingWhiteSpace: string, firstLineAlreadyIndented: boolean): void {
|
||||
// If the multiline comment spans multiple lines, we need to add the right indent amount to
|
||||
// each successive line segment as well.
|
||||
var position = fullStart;
|
||||
var segments = Syntax.splitMultiLineCommentTriviaIntoMultipleLines(trivia);
|
||||
|
||||
if (segments.length <= 1) {
|
||||
if (!firstLineAlreadyIndented) {
|
||||
// Process the one-line multiline comment just like a single line comment
|
||||
this.recordIndentationEditsForSingleLineOrSkippedText(trivia, fullStart, indentationString);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Find number of columns in first segment
|
||||
var whiteSpaceColumnsInFirstSegment = Indentation.columnForPositionInString(leadingWhiteSpace, leadingWhiteSpace.length, this.options);
|
||||
|
||||
var indentationColumns = Indentation.columnForPositionInString(indentationString, indentationString.length, this.options);
|
||||
var startIndex = 0;
|
||||
if (firstLineAlreadyIndented) {
|
||||
startIndex = 1;
|
||||
position += segments[0].length;
|
||||
}
|
||||
for (var i = startIndex; i < segments.length; i++) {
|
||||
var segment = segments[i];
|
||||
this.recordIndentationEditsForSegment(segment, position, indentationColumns, whiteSpaceColumnsInFirstSegment);
|
||||
position += segment.length;
|
||||
}
|
||||
}
|
||||
|
||||
private recordIndentationEditsForSegment(segment: string, fullStart: number, indentationColumns: number, whiteSpaceColumnsInFirstSegment: number): void {
|
||||
// Indent subsequent lines using a column delta of the actual indentation relative to the first line
|
||||
var firstNonWhitespacePosition = Indentation.firstNonWhitespacePosition(segment);
|
||||
var leadingWhiteSpaceColumns = Indentation.columnForPositionInString(segment, firstNonWhitespacePosition, this.options);
|
||||
var deltaFromFirstSegment = leadingWhiteSpaceColumns - whiteSpaceColumnsInFirstSegment;
|
||||
var finalColumns = indentationColumns + deltaFromFirstSegment;
|
||||
if (finalColumns < 0) {
|
||||
finalColumns = 0;
|
||||
}
|
||||
var indentationString = Indentation.indentationString(finalColumns, this.options);
|
||||
|
||||
if (firstNonWhitespacePosition < segment.length &&
|
||||
CharacterInfo.isLineTerminator(segment.charCodeAt(firstNonWhitespacePosition))) {
|
||||
// If this segment was just a newline, then don't bother indenting it. That will just
|
||||
// leave the user with an ugly indent in their output that they probably do not want.
|
||||
return;
|
||||
}
|
||||
|
||||
if (indentationString === segment.substring(0, firstNonWhitespacePosition)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Record the edit
|
||||
this.recordEdit(fullStart, firstNonWhitespacePosition, indentationString);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/services/formatting/new/rule.ts
Normal file
32
src/services/formatting/new/rule.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
module ts.formatting {
|
||||
export class Rule {
|
||||
constructor(
|
||||
public Descriptor: RuleDescriptor,
|
||||
public Operation: RuleOperation,
|
||||
public Flag: RuleFlags = RuleFlags.None) {
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return "[desc=" + this.Descriptor + "," +
|
||||
"operation=" + this.Operation + "," +
|
||||
"flag=" + this.Flag + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/services/formatting/new/ruleAction.ts
Normal file
25
src/services/formatting/new/ruleAction.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
module ts.formatting {
|
||||
export enum RuleAction {
|
||||
Ignore = 0x00000001,
|
||||
Space = 0x00000002,
|
||||
NewLine = 0x00000004,
|
||||
Delete = 0x00000008
|
||||
}
|
||||
}
|
||||
46
src/services/formatting/new/ruleDescriptor.ts
Normal file
46
src/services/formatting/new/ruleDescriptor.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
module 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
|
||||
//: this(TokenRange.FromToken(left), right)
|
||||
{
|
||||
return RuleDescriptor.create4(Shared.TokenRange.FromToken(left), right);
|
||||
}
|
||||
|
||||
static create4(left: Shared.TokenRange, right: Shared.TokenRange): RuleDescriptor {
|
||||
return new RuleDescriptor(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/services/formatting/new/ruleFlag.ts
Normal file
23
src/services/formatting/new/ruleFlag.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
module ts.formatting {
|
||||
export enum RuleFlags {
|
||||
None,
|
||||
CanDeleteNewLines
|
||||
}
|
||||
}
|
||||
44
src/services/formatting/new/ruleOperation.ts
Normal file
44
src/services/formatting/new/ruleOperation.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
module ts.formatting {
|
||||
export class RuleOperation {
|
||||
public Context: RuleOperationContext;
|
||||
public Action: RuleAction;
|
||||
|
||||
constructor() {
|
||||
this.Context = null;
|
||||
this.Action = null;
|
||||
}
|
||||
|
||||
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) {
|
||||
var result = new RuleOperation();
|
||||
result.Context = context;
|
||||
result.Action = action;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/services/formatting/new/ruleOperationContext.ts
Normal file
47
src/services/formatting/new/ruleOperationContext.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
module ts.formatting {
|
||||
|
||||
export class RuleOperationContext {
|
||||
private customContextChecks: { (context: FormattingContext): boolean; }[];
|
||||
|
||||
constructor(...funcs: { (context: FormattingContext): boolean; }[]) {
|
||||
this.customContextChecks = funcs;
|
||||
}
|
||||
|
||||
static Any: RuleOperationContext = new RuleOperationContext();
|
||||
|
||||
|
||||
public IsAny(): boolean {
|
||||
return this == RuleOperationContext.Any;
|
||||
}
|
||||
|
||||
public InContext(context: FormattingContext): boolean {
|
||||
if (this.IsAny()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (var i = 0, len = this.customContextChecks.length; i < len; i++) {
|
||||
if (!this.customContextChecks[i](context)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
687
src/services/formatting/new/rules.ts
Normal file
687
src/services/formatting/new/rules.ts
Normal file
@@ -0,0 +1,687 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
module ts.formatting {
|
||||
export class Rules {
|
||||
public getRuleName(rule: Rule) {
|
||||
var o: ts.Map<any> = <any>this;
|
||||
for (var name in o) {
|
||||
if (o[name] === rule) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
throw new Error(TypeScript.getDiagnosticMessage(TypeScript.DiagnosticCode.Unknown_rule, null));
|
||||
}
|
||||
|
||||
[name: string]: any;
|
||||
|
||||
public IgnoreBeforeComment: Rule;
|
||||
public IgnoreAfterLineComment: Rule;
|
||||
|
||||
// Space after keyword but not before ; or : or ?
|
||||
public NoSpaceBeforeSemicolon: Rule;
|
||||
public NoSpaceBeforeColon: Rule;
|
||||
public NoSpaceBeforeQMark: Rule;
|
||||
public SpaceAfterColon: Rule;
|
||||
public SpaceAfterQMark: Rule;
|
||||
public SpaceAfterSemicolon: Rule;
|
||||
|
||||
// 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 indexer and dot
|
||||
public NoSpaceBeforeDot: Rule;
|
||||
public NoSpaceAfterDot: Rule;
|
||||
public NoSpaceBeforeOpenBracket: Rule;
|
||||
public NoSpaceAfterOpenBracket: Rule;
|
||||
public NoSpaceBeforeCloseBracket: 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 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 NoSpaceBeforeOpenParenInFuncCall: Rule;
|
||||
public SpaceAfterFunctionInFuncDecl: 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 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 SpaceAfterArrow: Rule;
|
||||
|
||||
// Optional parameters and var 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 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
|
||||
//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;
|
||||
|
||||
// Open Brace braces after TypeScript module/class/interface
|
||||
public TypeScriptOpenBraceLeftTokenRange: Shared.TokenRange;
|
||||
public SpaceBeforeOpenBraceInTypeScriptDeclWithBlock: Rule;
|
||||
public NewLineBeforeOpenBraceInTypeScriptDeclWithBlock: Rule;
|
||||
|
||||
// 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 NoSpaceBetweenParens: Rule;
|
||||
public NoSpaceAfterOpenParen: Rule;
|
||||
public NoSpaceBeforeCloseParen: Rule;
|
||||
|
||||
// Insert space after function keyword for anonymous functions
|
||||
public SpaceAfterAnonymousFunctionKeyword: Rule;
|
||||
public NoSpaceAfterAnonymousFunctionKeyword: Rule;
|
||||
|
||||
constructor() {
|
||||
///
|
||||
/// Common Rules
|
||||
///
|
||||
|
||||
// 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));
|
||||
|
||||
// Space after keyword but not before ; or : or ?
|
||||
this.NoSpaceBeforeSemicolon = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.SemicolonToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
|
||||
this.NoSpaceBeforeColon = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.ColonToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsNotBinaryOpContext), RuleAction.Delete));
|
||||
this.NoSpaceBeforeQMark = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.QuestionToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsNotBinaryOpContext), RuleAction.Delete));
|
||||
this.SpaceAfterColon = new Rule(RuleDescriptor.create3(SyntaxKind.ColonToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsNotBinaryOpContext), RuleAction.Space));
|
||||
this.SpaceAfterQMark = new Rule(RuleDescriptor.create3(SyntaxKind.QuestionToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsNotBinaryOpContext), RuleAction.Space));
|
||||
this.SpaceAfterSemicolon = new Rule(RuleDescriptor.create3(SyntaxKind.SemicolonToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Space));
|
||||
|
||||
// Space after }.
|
||||
this.SpaceAfterCloseBrace = new Rule(RuleDescriptor.create3(SyntaxKind.CloseBraceToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.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
|
||||
this.SpaceBetweenCloseBraceAndElse = new Rule(RuleDescriptor.create1(SyntaxKind.CloseBraceToken, SyntaxKind.ElseKeyword), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Space));
|
||||
this.SpaceBetweenCloseBraceAndWhile = new Rule(RuleDescriptor.create1(SyntaxKind.CloseBraceToken, SyntaxKind.WhileKeyword), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Space));
|
||||
this.NoSpaceAfterCloseBrace = new Rule(RuleDescriptor.create3(SyntaxKind.CloseBraceToken, Shared.TokenRange.FromTokens([SyntaxKind.CloseParenToken, SyntaxKind.CloseBracketToken, SyntaxKind.CommaToken, SyntaxKind.SemicolonToken])), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
|
||||
|
||||
// No space for indexer and dot
|
||||
this.NoSpaceBeforeDot = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.DotToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
|
||||
this.NoSpaceAfterDot = new Rule(RuleDescriptor.create3(SyntaxKind.DotToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
|
||||
this.NoSpaceBeforeOpenBracket = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.OpenBracketToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
|
||||
this.NoSpaceAfterOpenBracket = new Rule(RuleDescriptor.create3(SyntaxKind.OpenBracketToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
|
||||
this.NoSpaceBeforeCloseBracket = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseBracketToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
|
||||
this.NoSpaceAfterCloseBracket = new Rule(RuleDescriptor.create3(SyntaxKind.CloseBracketToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), 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.IsFunctionDeclContext, Rules.IsNotFormatOnEnter, Rules.IsSameLineTokenOrBeforeMultilineBlockContext), RuleAction.Space), RuleFlags.CanDeleteNewLines);
|
||||
|
||||
// Place a space before open brace in a TypeScript declaration that has braces as children (class, module, enum, etc)
|
||||
this.TypeScriptOpenBraceLeftTokenRange = Shared.TokenRange.FromTokens([SyntaxKind.Identifier, SyntaxKind.MultiLineCommentTrivia]);
|
||||
this.SpaceBeforeOpenBraceInTypeScriptDeclWithBlock = new Rule(RuleDescriptor.create2(this.TypeScriptOpenBraceLeftTokenRange, SyntaxKind.OpenBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsTypeScriptDeclWithBlockContext, Rules.IsNotFormatOnEnter, Rules.IsSameLineTokenOrBeforeMultilineBlockContext), RuleAction.Space), RuleFlags.CanDeleteNewLines);
|
||||
|
||||
// Place a space before open brace in a control flow construct
|
||||
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.IsControlDeclContext, Rules.IsNotFormatOnEnter, Rules.IsSameLineTokenOrBeforeMultilineBlockContext), 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.IsSingleLineBlockContext), RuleAction.Space));
|
||||
this.SpaceBeforeCloseBrace = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSingleLineBlockContext), RuleAction.Space));
|
||||
this.NoSpaceBetweenEmptyBraceBrackets = new Rule(RuleDescriptor.create1(SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, 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));
|
||||
|
||||
// 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.IsSameLineTokenContext, Rules.IsNotBinaryOpContext), RuleAction.Delete));
|
||||
this.NoSpaceAfterUnaryPreincrementOperator = new Rule(RuleDescriptor.create3(SyntaxKind.PlusPlusToken, Shared.TokenRange.UnaryPreincrementExpressions), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
|
||||
this.NoSpaceAfterUnaryPredecrementOperator = new Rule(RuleDescriptor.create3(SyntaxKind.MinusMinusToken, Shared.TokenRange.UnaryPredecrementExpressions), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
|
||||
this.NoSpaceBeforeUnaryPostincrementOperator = new Rule(RuleDescriptor.create2(Shared.TokenRange.UnaryPostincrementExpressions, SyntaxKind.PlusPlusToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
|
||||
this.NoSpaceBeforeUnaryPostdecrementOperator = new Rule(RuleDescriptor.create2(Shared.TokenRange.UnaryPostdecrementExpressions, SyntaxKind.MinusMinusToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), 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.IsSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space));
|
||||
this.SpaceAfterAddWhenFollowedByUnaryPlus = new Rule(RuleDescriptor.create1(SyntaxKind.PlusToken, SyntaxKind.PlusToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space));
|
||||
this.SpaceAfterAddWhenFollowedByPreincrement = new Rule(RuleDescriptor.create1(SyntaxKind.PlusToken, SyntaxKind.PlusPlusToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space));
|
||||
this.SpaceAfterPostdecrementWhenFollowedBySubtract = new Rule(RuleDescriptor.create1(SyntaxKind.MinusMinusToken, SyntaxKind.MinusToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space));
|
||||
this.SpaceAfterSubtractWhenFollowedByUnaryMinus = new Rule(RuleDescriptor.create1(SyntaxKind.MinusToken, SyntaxKind.MinusToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space));
|
||||
this.SpaceAfterSubtractWhenFollowedByPredecrement = new Rule(RuleDescriptor.create1(SyntaxKind.MinusToken, SyntaxKind.MinusMinusToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space));
|
||||
|
||||
this.NoSpaceBeforeComma = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CommaToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
|
||||
|
||||
this.SpaceAfterCertainKeywords = new Rule(RuleDescriptor.create4(Shared.TokenRange.FromTokens([SyntaxKind.VarKeyword, SyntaxKind.ThrowKeyword, SyntaxKind.NewKeyword, SyntaxKind.DeleteKeyword, SyntaxKind.ReturnKeyword, SyntaxKind.TypeOfKeyword]), Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Space));
|
||||
this.NoSpaceBeforeOpenParenInFuncCall = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsFunctionCallOrNewContext), RuleAction.Delete));
|
||||
this.SpaceAfterFunctionInFuncDecl = new Rule(RuleDescriptor.create3(SyntaxKind.FunctionKeyword, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsFunctionDeclContext), RuleAction.Space));
|
||||
this.NoSpaceBeforeOpenParenInFuncDecl = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsFunctionDeclContext), RuleAction.Delete));
|
||||
this.SpaceAfterVoidOperator = new Rule(RuleDescriptor.create3(SyntaxKind.VoidKeyword, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsVoidOpContext), RuleAction.Space));
|
||||
|
||||
this.NoSpaceBetweenReturnAndSemicolon = new Rule(RuleDescriptor.create1(SyntaxKind.ReturnKeyword, SyntaxKind.SemicolonToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
|
||||
|
||||
// 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.IsSameLineTokenContext, 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.IsSameLineTokenContext), RuleAction.Space));
|
||||
|
||||
// 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));
|
||||
|
||||
// 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.IsSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space));
|
||||
this.SpaceAfterBinaryKeywordOperator = new Rule(RuleDescriptor.create4(Shared.TokenRange.BinaryKeywordOperators, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space));
|
||||
|
||||
// TypeScript-specific higher priority rules
|
||||
|
||||
// Treat constructor as an identifier in a function declaration, and remove spaces between constructor and following left parentheses
|
||||
this.NoSpaceAfterConstructor = new Rule(RuleDescriptor.create1(SyntaxKind.ConstructorKeyword, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), 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.IsSameLineTokenContext), RuleAction.Delete));
|
||||
|
||||
// Add a space around certain TypeScript keywords
|
||||
this.SpaceAfterCertainTypeScriptKeywords = new Rule(RuleDescriptor.create4(Shared.TokenRange.FromTokens([SyntaxKind.ClassKeyword, SyntaxKind.DeclareKeyword, SyntaxKind.EnumKeyword, SyntaxKind.ExportKeyword, SyntaxKind.ExtendsKeyword, SyntaxKind.GetKeyword, SyntaxKind.ImplementsKeyword, SyntaxKind.ImportKeyword, SyntaxKind.InterfaceKeyword, SyntaxKind.ModuleKeyword, SyntaxKind.PrivateKeyword, SyntaxKind.PublicKeyword, SyntaxKind.SetKeyword, SyntaxKind.StaticKeyword]), Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Space));
|
||||
this.SpaceBeforeCertainTypeScriptKeywords = new Rule(RuleDescriptor.create4(Shared.TokenRange.Any, Shared.TokenRange.FromTokens([SyntaxKind.ExtendsKeyword, SyntaxKind.ImplementsKeyword])), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), 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.SpaceAfterArrow = new Rule(RuleDescriptor.create3(SyntaxKind.EqualsGreaterThanToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Space));
|
||||
|
||||
// Optional parameters and var args
|
||||
this.NoSpaceAfterEllipsis = new Rule(RuleDescriptor.create1(SyntaxKind.DotDotDotToken, SyntaxKind.Identifier), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
|
||||
this.NoSpaceAfterOptionalParameters = new Rule(RuleDescriptor.create3(SyntaxKind.QuestionToken, Shared.TokenRange.FromTokens([SyntaxKind.CloseParenToken, SyntaxKind.CommaToken])), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsNotBinaryOpContext), RuleAction.Delete));
|
||||
|
||||
// generics
|
||||
this.NoSpaceBeforeOpenAngularBracket = new Rule(RuleDescriptor.create2(Shared.TokenRange.TypeNames, SyntaxKind.LessThanToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsTypeArgumentOrParameterContext), RuleAction.Delete));
|
||||
this.NoSpaceBetweenCloseParenAndAngularBracket = new Rule(RuleDescriptor.create1(SyntaxKind.CloseParenToken, SyntaxKind.LessThanToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsTypeArgumentOrParameterContext), RuleAction.Delete));
|
||||
this.NoSpaceAfterOpenAngularBracket = new Rule(RuleDescriptor.create3(SyntaxKind.LessThanToken, Shared.TokenRange.TypeNames), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsTypeArgumentOrParameterContext), RuleAction.Delete));
|
||||
this.NoSpaceBeforeCloseAngularBracket = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.GreaterThanToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsTypeArgumentOrParameterContext), 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.IsSameLineTokenContext, Rules.IsTypeArgumentOrParameterContext), 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.IsSameLineTokenContext, Rules.IsObjectTypeContext), RuleAction.Delete));
|
||||
|
||||
// These rules are higher in priority than user-configurable rules.
|
||||
this.HighPriorityCommonRules =
|
||||
[
|
||||
this.IgnoreBeforeComment, this.IgnoreAfterLineComment,
|
||||
this.NoSpaceBeforeColon, this.SpaceAfterColon, this.NoSpaceBeforeQMark, this.SpaceAfterQMark,
|
||||
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.SpaceAfterOpenBrace, this.SpaceBeforeCloseBrace, this.NewLineBeforeCloseBraceInBlockContext,
|
||||
this.SpaceAfterCloseBrace, this.SpaceBetweenCloseBraceAndElse, this.SpaceBetweenCloseBraceAndWhile, this.NoSpaceBetweenEmptyBraceBrackets,
|
||||
this.SpaceAfterFunctionInFuncDecl, this.NewLineAfterOpenBraceInBlockContext, this.SpaceAfterGetSetInMember,
|
||||
this.NoSpaceBetweenReturnAndSemicolon,
|
||||
this.SpaceAfterCertainKeywords,
|
||||
this.NoSpaceBeforeOpenParenInFuncCall,
|
||||
this.SpaceBeforeBinaryKeywordOperator, this.SpaceAfterBinaryKeywordOperator,
|
||||
this.SpaceAfterVoidOperator,
|
||||
|
||||
// TypeScript-specific rules
|
||||
this.NoSpaceAfterConstructor, this.NoSpaceAfterModuleImport,
|
||||
this.SpaceAfterCertainTypeScriptKeywords, this.SpaceBeforeCertainTypeScriptKeywords,
|
||||
this.SpaceAfterModuleName,
|
||||
this.SpaceAfterArrow,
|
||||
this.NoSpaceAfterEllipsis,
|
||||
this.NoSpaceAfterOptionalParameters,
|
||||
this.NoSpaceBetweenEmptyInterfaceBraceBrackets,
|
||||
this.NoSpaceBeforeOpenAngularBracket,
|
||||
this.NoSpaceBetweenCloseParenAndAngularBracket,
|
||||
this.NoSpaceAfterOpenAngularBracket,
|
||||
this.NoSpaceBeforeCloseAngularBracket,
|
||||
this.NoSpaceAfterCloseAngularBracket
|
||||
];
|
||||
|
||||
// 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.NoSpaceAfterOpenBracket,
|
||||
this.NoSpaceBeforeCloseBracket, this.NoSpaceAfterCloseBracket,
|
||||
this.SpaceAfterSemicolon,
|
||||
this.NoSpaceBeforeOpenParenInFuncDecl,
|
||||
this.SpaceBetweenStatements, this.SpaceAfterTryFinally
|
||||
];
|
||||
|
||||
///
|
||||
/// Rules controlled by user options
|
||||
///
|
||||
|
||||
// Insert space after comma delimiter
|
||||
this.SpaceAfterComma = new Rule(RuleDescriptor.create3(SyntaxKind.CommaToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Space));
|
||||
this.NoSpaceAfterComma = new Rule(RuleDescriptor.create3(SyntaxKind.CommaToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), 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.IsSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space));
|
||||
this.SpaceAfterBinaryOperator = new Rule(RuleDescriptor.create4(Shared.TokenRange.BinaryOperators, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Space));
|
||||
this.NoSpaceBeforeBinaryOperator = new Rule(RuleDescriptor.create4(Shared.TokenRange.Any, Shared.TokenRange.BinaryOperators), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsBinaryOpContext), RuleAction.Delete));
|
||||
this.NoSpaceAfterBinaryOperator = new Rule(RuleDescriptor.create4(Shared.TokenRange.BinaryOperators, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsBinaryOpContext), 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.IsControlDeclContext), RuleAction.Space));
|
||||
this.NoSpaceAfterKeywordInControl = new Rule(RuleDescriptor.create2(Shared.TokenRange.Keywords, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsControlDeclContext), RuleAction.Delete));
|
||||
|
||||
// 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.IsFunctionDeclContext, Rules.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.IsTypeScriptDeclWithBlockContext, Rules.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.IsControlDeclContext, Rules.IsBeforeMultilineBlockContext), RuleAction.NewLine), RuleFlags.CanDeleteNewLines);
|
||||
|
||||
// Insert space after semicolon in for statement
|
||||
this.SpaceAfterSemicolonInFor = new Rule(RuleDescriptor.create3(SyntaxKind.SemicolonToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsForContext), RuleAction.Space));
|
||||
this.NoSpaceAfterSemicolonInFor = new Rule(RuleDescriptor.create3(SyntaxKind.SemicolonToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext, Rules.IsForContext), 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.IsSameLineTokenContext), RuleAction.Space));
|
||||
this.SpaceBeforeCloseParen = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Space));
|
||||
this.NoSpaceBetweenParens = new Rule(RuleDescriptor.create1(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
|
||||
this.NoSpaceAfterOpenParen = new Rule(RuleDescriptor.create3(SyntaxKind.OpenParenToken, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
|
||||
this.NoSpaceBeforeCloseParen = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CloseParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsSameLineTokenContext), RuleAction.Delete));
|
||||
|
||||
// Insert space after function keyword for anonymous functions
|
||||
this.SpaceAfterAnonymousFunctionKeyword = new Rule(RuleDescriptor.create1(SyntaxKind.FunctionKeyword, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsFunctionDeclContext), RuleAction.Space));
|
||||
this.NoSpaceAfterAnonymousFunctionKeyword = new Rule(RuleDescriptor.create1(SyntaxKind.FunctionKeyword, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsFunctionDeclContext), RuleAction.Delete));
|
||||
}
|
||||
|
||||
///
|
||||
/// Contexts
|
||||
///
|
||||
|
||||
static IsForContext(context: FormattingContext): boolean {
|
||||
return context.contextNode.kind === SyntaxKind.ForStatement;
|
||||
}
|
||||
|
||||
static IsNotForContext(context: FormattingContext): boolean {
|
||||
return !Rules.IsForContext(context);
|
||||
}
|
||||
|
||||
static IsBinaryOpContext(context: FormattingContext): boolean {
|
||||
|
||||
switch (context.contextNode.kind) {
|
||||
case SyntaxKind.BinaryExpression:
|
||||
return true;
|
||||
//// binary expressions
|
||||
//case SyntaxKind.AssignmentExpression:
|
||||
//case SyntaxKind.AddAssignmentExpression:
|
||||
//case SyntaxKind.SubtractAssignmentExpression:
|
||||
//case SyntaxKind.MultiplyAssignmentExpression:
|
||||
//case SyntaxKind.DivideAssignmentExpression:
|
||||
//case SyntaxKind.ModuloAssignmentExpression:
|
||||
//case SyntaxKind.AndAssignmentExpression:
|
||||
//case SyntaxKind.ExclusiveOrAssignmentExpression:
|
||||
//case SyntaxKind.OrAssignmentExpression:
|
||||
//case SyntaxKind.LeftShiftAssignmentExpression:
|
||||
//case SyntaxKind.SignedRightShiftAssignmentExpression:
|
||||
//case SyntaxKind.UnsignedRightShiftAssignmentExpression:
|
||||
//case SyntaxKind.ConditionalExpression:
|
||||
//case SyntaxKind.LogicalOrExpression:
|
||||
//case SyntaxKind.LogicalAndExpression:
|
||||
//case SyntaxKind.BitwiseOrExpression:
|
||||
//case SyntaxKind.BitwiseExclusiveOrExpression:
|
||||
//case SyntaxKind.BitwiseAndExpression:
|
||||
//case SyntaxKind.EqualsWithTypeConversionExpression:
|
||||
//case SyntaxKind.NotEqualsWithTypeConversionExpression:
|
||||
//case SyntaxKind.EqualsExpression:
|
||||
//case SyntaxKind.NotEqualsExpression:
|
||||
//case SyntaxKind.LessThanExpression:
|
||||
//case SyntaxKind.GreaterThanExpression:
|
||||
//case SyntaxKind.LessThanOrEqualExpression:
|
||||
//case SyntaxKind.GreaterThanOrEqualExpression:
|
||||
//case SyntaxKind.InstanceOfExpression:
|
||||
//case SyntaxKind.InExpression:
|
||||
//case SyntaxKind.LeftShiftExpression:
|
||||
//case SyntaxKind.SignedRightShiftExpression:
|
||||
//case SyntaxKind.UnsignedRightShiftExpression:
|
||||
//case SyntaxKind.MultiplyExpression:
|
||||
//case SyntaxKind.DivideExpression:
|
||||
//case SyntaxKind.ModuloExpression:
|
||||
//case SyntaxKind.AddExpression:
|
||||
//case SyntaxKind.SubtractExpression:
|
||||
// return true;
|
||||
|
||||
// equal in import a = module('a');
|
||||
case SyntaxKind.ImportDeclaration:
|
||||
// equal in var a = 0;
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
// TODO:
|
||||
//case SyntaxKind.EqualsValueClause:
|
||||
return context.currentTokenSpan.kind === SyntaxKind.EqualsToken || context.nextTokenSpan.kind === SyntaxKind.EqualsToken;
|
||||
// "in" keyword in for (var x in []) { }
|
||||
case SyntaxKind.ForInStatement:
|
||||
return context.currentTokenSpan.kind === SyntaxKind.InKeyword || context.nextTokenSpan.kind === SyntaxKind.InKeyword;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static IsNotBinaryOpContext(context: FormattingContext): boolean {
|
||||
return !Rules.IsBinaryOpContext(context);
|
||||
}
|
||||
|
||||
static IsSameLineTokenOrBeforeMultilineBlockContext(context: FormattingContext): boolean {
|
||||
//// This check is mainly used inside SpaceBeforeOpenBraceInControl and SpaceBeforeOpenBraceInFunction.
|
||||
////
|
||||
//// Ex:
|
||||
//// if (1) { ....
|
||||
//// * ) and { are on the same line so apply the rule. Here we don't care whether it's same or multi block context
|
||||
////
|
||||
//// Ex:
|
||||
//// if (1)
|
||||
//// { ... }
|
||||
//// * ) and { are on differnet lines. We only need to format if the block is multiline context. So in this case we don't format.
|
||||
////
|
||||
//// Ex:
|
||||
//// if (1)
|
||||
//// { ...
|
||||
//// }
|
||||
//// * ) and { are on differnet lines. We only need to format if the block is multiline context. So in this case we format.
|
||||
|
||||
return context.TokensAreOnSameLine() || Rules.IsBeforeMultilineBlockContext(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).
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.Block:
|
||||
case SyntaxKind.SwitchStatement:
|
||||
case SyntaxKind.ObjectLiteral:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static IsFunctionDeclContext(context: FormattingContext): boolean {
|
||||
switch (context.contextNode.kind) {
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.Method:
|
||||
//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 IsTypeScriptDeclWithBlockContext(context: FormattingContext): boolean {
|
||||
return Rules.NodeIsTypeScriptDeclWithBlockContext(context.contextNode);
|
||||
}
|
||||
|
||||
static NodeIsTypeScriptDeclWithBlockContext(node: Node): boolean {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
case SyntaxKind.TypeLiteral:
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static IsAfterCodeBlockContext(context: FormattingContext): boolean {
|
||||
switch (context.currentTokenParent.kind) {
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
case SyntaxKind.Block:
|
||||
case SyntaxKind.SwitchStatement:
|
||||
return true;
|
||||
}
|
||||
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.WhileStatement:
|
||||
case SyntaxKind.TryStatement:
|
||||
case SyntaxKind.DoStatement:
|
||||
case SyntaxKind.WithStatement:
|
||||
// TODO
|
||||
// case SyntaxKind.ElseClause:
|
||||
case SyntaxKind.CatchBlock:
|
||||
case SyntaxKind.FinallyBlock:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static IsObjectContext(context: FormattingContext): boolean {
|
||||
return context.contextNode.kind === SyntaxKind.ObjectLiteral;
|
||||
}
|
||||
|
||||
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 IsSameLineTokenContext(context: FormattingContext): boolean {
|
||||
return context.TokensAreOnSameLine();
|
||||
}
|
||||
|
||||
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 IsTypeArgumentOrParameter(tokenKind: SyntaxKind, parentKind: SyntaxKind): boolean {
|
||||
return;
|
||||
//return ((tokenKind === SyntaxKind.LessThanToken || tokenKind === SyntaxKind.GreaterThanToken) &&
|
||||
// (parentKind === SyntaxKind.TypeParameterList || parentKind === SyntaxKind.TypeArgumentList));
|
||||
}
|
||||
|
||||
static IsTypeArgumentOrParameterContext(context: FormattingContext): boolean {
|
||||
return Rules.IsTypeArgumentOrParameter(context.currentTokenSpan.kind, context.currentTokenParent.kind) ||
|
||||
Rules.IsTypeArgumentOrParameter(context.nextTokenSpan.kind, context.nextTokenParent.kind);
|
||||
}
|
||||
|
||||
static IsVoidOpContext(context: FormattingContext): boolean {
|
||||
return;
|
||||
//return context.currentTokenSpan.token.kind === SyntaxKind.VoidKeyword && context.currentTokenParent.kind() === SyntaxKind.VoidExpression;
|
||||
}
|
||||
}
|
||||
}
|
||||
189
src/services/formatting/new/rulesMap.ts
Normal file
189
src/services/formatting/new/rulesMap.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
module ts.formatting {
|
||||
export class RulesMap {
|
||||
public map: RulesBucket[];
|
||||
public mapRowLength: number;
|
||||
|
||||
constructor() {
|
||||
this.map = [];
|
||||
this.mapRowLength = 0;
|
||||
}
|
||||
|
||||
static create(rules: Rule[]): RulesMap {
|
||||
var result = new RulesMap();
|
||||
result.Initialize(rules);
|
||||
return result;
|
||||
}
|
||||
|
||||
public Initialize(rules: Rule[]) {
|
||||
this.mapRowLength = SyntaxKind.LastToken + 1;
|
||||
this.map = <any> new Array(this.mapRowLength * this.mapRowLength);//new Array<RulesBucket>(this.mapRowLength * this.mapRowLength);
|
||||
|
||||
// This array is used only during construction of the rulesbucket in the map
|
||||
var rulesBucketConstructionStateList: RulesBucketConstructionState[] = <any> new Array(this.map.length);//new Array<RulesBucketConstructionState>(this.map.length);
|
||||
|
||||
this.FillRules(rules, rulesBucketConstructionStateList);
|
||||
return this.map;
|
||||
}
|
||||
|
||||
public FillRules(rules: Rule[], rulesBucketConstructionStateList: RulesBucketConstructionState[]): void {
|
||||
rules.forEach((rule) => {
|
||||
this.FillRule(rule, rulesBucketConstructionStateList);
|
||||
});
|
||||
}
|
||||
|
||||
private GetRuleBucketIndex(row: number, column: number): number {
|
||||
var rulesBucketIndex = (row * this.mapRowLength) + column;
|
||||
//Debug.Assert(rulesBucketIndex < this.map.Length, "Trying to access an index outside the array.");
|
||||
return rulesBucketIndex;
|
||||
}
|
||||
|
||||
private FillRule(rule: Rule, rulesBucketConstructionStateList: RulesBucketConstructionState[]): void {
|
||||
var specificRule = rule.Descriptor.LeftTokenRange != Shared.TokenRange.Any &&
|
||||
rule.Descriptor.RightTokenRange != Shared.TokenRange.Any;
|
||||
|
||||
rule.Descriptor.LeftTokenRange.GetTokens().forEach((left) => {
|
||||
rule.Descriptor.RightTokenRange.GetTokens().forEach((right) => {
|
||||
var rulesBucketIndex = this.GetRuleBucketIndex(left, right);
|
||||
|
||||
var rulesBucket = this.map[rulesBucketIndex];
|
||||
if (rulesBucket == undefined) {
|
||||
rulesBucket = this.map[rulesBucketIndex] = new RulesBucket();
|
||||
}
|
||||
|
||||
rulesBucket.AddRule(rule, specificRule, rulesBucketConstructionStateList, rulesBucketIndex);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public GetRule(context: FormattingContext): Rule {
|
||||
var bucketIndex = this.GetRuleBucketIndex(context.currentTokenSpan.kind, context.nextTokenSpan.kind);
|
||||
var bucket = this.map[bucketIndex];
|
||||
if (bucket != null) {
|
||||
for (var i = 0, len = bucket.Rules().length; i < len; i++) {
|
||||
var rule = bucket.Rules()[i];
|
||||
if (rule.Operation.Context.InContext(context))
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var MaskBitSize = 5;
|
||||
var Mask = 0x1f;
|
||||
|
||||
export enum RulesPosition {
|
||||
IgnoreRulesSpecific = 0,
|
||||
IgnoreRulesAny = MaskBitSize * 1,
|
||||
ContextRulesSpecific = MaskBitSize * 2,
|
||||
ContextRulesAny = MaskBitSize * 3,
|
||||
NoContextRulesSpecific = MaskBitSize * 4,
|
||||
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 {
|
||||
var index = 0;
|
||||
|
||||
var pos = 0;
|
||||
var indexBitmap = this.rulesInsertionIndexBitmap;
|
||||
|
||||
while (pos <= maskPosition) {
|
||||
index += (indexBitmap & Mask);
|
||||
indexBitmap >>= MaskBitSize;
|
||||
pos += MaskBitSize;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public IncreaseInsertionIndex(maskPosition: RulesPosition): void {
|
||||
var 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.");
|
||||
|
||||
var temp = this.rulesInsertionIndexBitmap & ~(Mask << maskPosition);
|
||||
temp |= value << maskPosition;
|
||||
|
||||
this.rulesInsertionIndexBitmap = temp;
|
||||
}
|
||||
}
|
||||
|
||||
export class RulesBucket {
|
||||
private rules: Rule[];
|
||||
|
||||
constructor() {
|
||||
this.rules = [];
|
||||
}
|
||||
|
||||
public Rules(): Rule[] {
|
||||
return this.rules;
|
||||
}
|
||||
|
||||
public AddRule(rule: Rule, specificTokens: boolean, constructionState: RulesBucketConstructionState[], rulesBucketIndex: number): void {
|
||||
var 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;
|
||||
}
|
||||
|
||||
var state = constructionState[rulesBucketIndex];
|
||||
if (state === undefined) {
|
||||
state = constructionState[rulesBucketIndex] = new RulesBucketConstructionState();
|
||||
}
|
||||
var index = state.GetInsertionIndex(position);
|
||||
this.rules.splice(index, 0, rule);
|
||||
state.IncreaseInsertionIndex(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
117
src/services/formatting/new/rulesProvider.ts
Normal file
117
src/services/formatting/new/rulesProvider.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
/// <reference path="formatting.ts"/>
|
||||
|
||||
module ts.formatting {
|
||||
export class RulesProvider {
|
||||
private globalRules: Rules;
|
||||
private options: ts.FormatCodeOptions;
|
||||
private activeRules: Rule[];
|
||||
private rulesMap: RulesMap;
|
||||
|
||||
constructor(private logger: TypeScript.Logger) {
|
||||
this.globalRules = new Rules();
|
||||
}
|
||||
|
||||
public getRuleName(rule: Rule): string {
|
||||
return this.globalRules.getRuleName(rule);
|
||||
}
|
||||
|
||||
public getRuleByName(name: string): Rule {
|
||||
return this.globalRules[name];
|
||||
}
|
||||
|
||||
public getRulesMap() {
|
||||
return this.rulesMap;
|
||||
}
|
||||
|
||||
public ensureUpToDate(options: ts.FormatCodeOptions) {
|
||||
if (this.options == null || !ts.compareDataObjects(this.options, options)) {
|
||||
var activeRules = this.createActiveRules(options);
|
||||
var rulesMap = RulesMap.create(activeRules);
|
||||
|
||||
this.activeRules = activeRules;
|
||||
this.rulesMap = rulesMap;
|
||||
this.options = ts.clone(options);
|
||||
}
|
||||
}
|
||||
|
||||
private createActiveRules(options: ts.FormatCodeOptions): Rule[] {
|
||||
var rules = this.globalRules.HighPriorityCommonRules.slice(0);
|
||||
|
||||
if (options.InsertSpaceAfterCommaDelimiter) {
|
||||
rules.push(this.globalRules.SpaceAfterComma);
|
||||
}
|
||||
else {
|
||||
rules.push(this.globalRules.NoSpaceAfterComma);
|
||||
}
|
||||
|
||||
if (options.InsertSpaceAfterFunctionKeywordForAnonymousFunctions) {
|
||||
rules.push(this.globalRules.SpaceAfterAnonymousFunctionKeyword);
|
||||
}
|
||||
else {
|
||||
rules.push(this.globalRules.NoSpaceAfterAnonymousFunctionKeyword);
|
||||
}
|
||||
|
||||
if (options.InsertSpaceAfterKeywordsInControlFlowStatements) {
|
||||
rules.push(this.globalRules.SpaceAfterKeywordInControl);
|
||||
}
|
||||
else {
|
||||
rules.push(this.globalRules.NoSpaceAfterKeywordInControl);
|
||||
}
|
||||
|
||||
if (options.InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis) {
|
||||
rules.push(this.globalRules.SpaceAfterOpenParen);
|
||||
rules.push(this.globalRules.SpaceBeforeCloseParen);
|
||||
rules.push(this.globalRules.NoSpaceBetweenParens);
|
||||
}
|
||||
else {
|
||||
rules.push(this.globalRules.NoSpaceAfterOpenParen);
|
||||
rules.push(this.globalRules.NoSpaceBeforeCloseParen);
|
||||
rules.push(this.globalRules.NoSpaceBetweenParens);
|
||||
}
|
||||
|
||||
if (options.InsertSpaceAfterSemicolonInForStatements) {
|
||||
rules.push(this.globalRules.SpaceAfterSemicolonInFor);
|
||||
}
|
||||
else {
|
||||
rules.push(this.globalRules.NoSpaceAfterSemicolonInFor);
|
||||
}
|
||||
|
||||
if (options.InsertSpaceBeforeAndAfterBinaryOperators) {
|
||||
rules.push(this.globalRules.SpaceBeforeBinaryOperator);
|
||||
rules.push(this.globalRules.SpaceAfterBinaryOperator);
|
||||
}
|
||||
else {
|
||||
rules.push(this.globalRules.NoSpaceBeforeBinaryOperator);
|
||||
rules.push(this.globalRules.NoSpaceAfterBinaryOperator);
|
||||
}
|
||||
|
||||
if (options.PlaceOpenBraceOnNewLineForControlBlocks) {
|
||||
rules.push(this.globalRules.NewLineBeforeOpenBraceInControl);
|
||||
}
|
||||
|
||||
if (options.PlaceOpenBraceOnNewLineForFunctions) {
|
||||
rules.push(this.globalRules.NewLineBeforeOpenBraceInFunction);
|
||||
rules.push(this.globalRules.NewLineBeforeOpenBraceInTypeScriptDeclWithBlock);
|
||||
}
|
||||
|
||||
rules = rules.concat(this.globalRules.LowPriorityCommonRules);
|
||||
|
||||
return rules;
|
||||
}
|
||||
}
|
||||
}
|
||||
401
src/services/formatting/new/smartIndenter.ts
Normal file
401
src/services/formatting/new/smartIndenter.ts
Normal file
@@ -0,0 +1,401 @@
|
||||
///<reference path='..\services.ts' />
|
||||
///<reference path='..\servicesSyntaxUtilities.ts' />
|
||||
|
||||
module ts.formatting {
|
||||
export module SmartIndenter {
|
||||
export function getIndentation(position: number, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
if (position > sourceFile.text.length) {
|
||||
return 0; // past EOF
|
||||
}
|
||||
|
||||
var precedingToken = ServicesSyntaxUtilities.findPrecedingToken(position, sourceFile);
|
||||
if (!precedingToken) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// no indentation in string \regex literals
|
||||
if ((precedingToken.kind === SyntaxKind.StringLiteral || precedingToken.kind === SyntaxKind.RegularExpressionLiteral) &&
|
||||
precedingToken.getStart(sourceFile) <= position &&
|
||||
precedingToken.end > position) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var lineAtPosition = sourceFile.getLineAndCharacterFromPosition(position).line;
|
||||
|
||||
if (precedingToken.kind === SyntaxKind.CommaToken && precedingToken.parent.kind !== SyntaxKind.BinaryExpression) {
|
||||
// previous token is comma that separates items in list - find the previous item and try to derive indentation from it
|
||||
var actualIndentation = getActualIndentationForListItemBeforeComma(precedingToken, sourceFile, options);
|
||||
if (actualIndentation !== -1) {
|
||||
return actualIndentation;
|
||||
}
|
||||
}
|
||||
|
||||
// try to find node that can contribute to indentation and includes 'position' starting from 'precedingToken'
|
||||
// if such node is found - compute initial indentation for 'position' inside this node
|
||||
var previous: Node;
|
||||
var current = precedingToken;
|
||||
var currentStart: LineAndCharacter;
|
||||
var indentationDelta: number;
|
||||
|
||||
while (current) {
|
||||
if (positionBelongsToNode(current, position, sourceFile) && nodeContentIsIndented(current, previous)) {
|
||||
currentStart = getStartLineAndCharacterForNode(current, sourceFile);
|
||||
|
||||
if (nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken, current, lineAtPosition, sourceFile)) {
|
||||
indentationDelta = 0;
|
||||
}
|
||||
else {
|
||||
indentationDelta = lineAtPosition !== currentStart.line ? options.IndentSize : 0;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// check if current node is a list item - if yes, take indentation from it
|
||||
var actualIndentation = getActualIndentationForListItem(current, sourceFile, options);
|
||||
if (actualIndentation !== -1) {
|
||||
return actualIndentation;
|
||||
}
|
||||
|
||||
previous = current;
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
if (!current) {
|
||||
// no parent was found - return 0 to be indented on the level of SourceFile
|
||||
return 0;
|
||||
}
|
||||
|
||||
return getIndentationForNode(current, currentStart, indentationDelta, sourceFile, options);
|
||||
}
|
||||
|
||||
export function getIndentationForNode(current: Node, currentStart: LineAndCharacter, indentationDelta: number, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
var parent: Node = current.parent;
|
||||
var parentStart: LineAndCharacter;
|
||||
|
||||
// walk upwards and collect indentations for pairs of parent-child nodes
|
||||
// indentation is not added if parent and child nodes start on the same line or if parent is IfStatement and child starts on the same line with 'else clause'
|
||||
while (parent) {
|
||||
// check if current node is a list item - if yes, take indentation from it
|
||||
var actualIndentation = getActualIndentationForListItem(current, sourceFile, options);
|
||||
if (actualIndentation !== -1) {
|
||||
return actualIndentation + indentationDelta;
|
||||
}
|
||||
|
||||
parentStart = sourceFile.getLineAndCharacterFromPosition(parent.getStart(sourceFile));
|
||||
var parentAndChildShareLine =
|
||||
parentStart.line === currentStart.line ||
|
||||
childStartsOnTheSameLineWithElseInIfStatement(parent, current, currentStart.line, sourceFile);
|
||||
|
||||
// try to fetch actual indentation for current node from source text
|
||||
var actualIndentation = getActualIndentationForNode(current, parent, currentStart, parentAndChildShareLine, sourceFile, options);
|
||||
if (actualIndentation !== -1) {
|
||||
return actualIndentation + indentationDelta;
|
||||
}
|
||||
|
||||
// increase indentation if parent node wants its content to be indented and parent and child nodes don't start on the same line
|
||||
if (nodeContentIsIndented(parent, current) && !parentAndChildShareLine) {
|
||||
indentationDelta += options.IndentSize;
|
||||
}
|
||||
|
||||
current = parent;
|
||||
currentStart = parentStart;
|
||||
parent = current.parent;
|
||||
}
|
||||
|
||||
return indentationDelta;
|
||||
}
|
||||
|
||||
/*
|
||||
* Function returns -1 if indentation cannot be determined
|
||||
*/
|
||||
function getActualIndentationForListItemBeforeComma(commaToken: Node, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
// previous token is comma that separates items in list - find the previous item and try to derive indentation from it
|
||||
var commaItemInfo = ServicesSyntaxUtilities.findListItemInfo(commaToken);
|
||||
Debug.assert(commaItemInfo.listItemIndex > 0);
|
||||
// The item we're interested in is right before the comma
|
||||
return deriveActualIndentationFromList(commaItemInfo.list.getChildren(), commaItemInfo.listItemIndex - 1, sourceFile, options);
|
||||
}
|
||||
|
||||
/*
|
||||
* Function returns -1 if actual indentation for node should not be used (i.e because node is nested expression)
|
||||
*/
|
||||
function getActualIndentationForNode(current: Node,
|
||||
parent: Node,
|
||||
currentLineAndChar: LineAndCharacter,
|
||||
parentAndChildShareLine: boolean,
|
||||
sourceFile: SourceFile,
|
||||
options: EditorOptions): number {
|
||||
|
||||
// actual indentation is used for statements\declarations if one of cases below is true:
|
||||
// - parent is SourceFile - by default immediate children of SourceFile are not indented except when user indents them manually
|
||||
// - parent and child are not on the same line
|
||||
var useActualIndentation =
|
||||
(isDeclaration(current) || isStatement(current)) &&
|
||||
(parent.kind === SyntaxKind.SourceFile || !parentAndChildShareLine);
|
||||
|
||||
if (!useActualIndentation) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return findColumnForFirstNonWhitespaceCharacterInLine(currentLineAndChar, sourceFile, options);
|
||||
}
|
||||
|
||||
function nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken: Node, current: Node, lineAtPosition: number, sourceFile: SourceFile): boolean {
|
||||
var nextToken = ServicesSyntaxUtilities.findNextToken(precedingToken, current);
|
||||
if (!nextToken) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nextToken.kind === SyntaxKind.OpenBraceToken) {
|
||||
// open braces are always indented at the parent level
|
||||
return true;
|
||||
}
|
||||
else if (nextToken.kind === SyntaxKind.CloseBraceToken) {
|
||||
// close braces are indented at the parent level if they are located on the same line with cursor
|
||||
// this means that if new line will be added at $ position, this case will be indented
|
||||
// class A {
|
||||
// $
|
||||
// }
|
||||
/// and this one - not
|
||||
// class A {
|
||||
// $}
|
||||
|
||||
var nextTokenStartLine = getStartLineAndCharacterForNode(nextToken, sourceFile).line;
|
||||
return lineAtPosition === nextTokenStartLine;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getStartLineAndCharacterForNode(n: Node, sourceFile: SourceFile): LineAndCharacter {
|
||||
return sourceFile.getLineAndCharacterFromPosition(n.getStart(sourceFile));
|
||||
}
|
||||
|
||||
function positionBelongsToNode(candidate: Node, position: number, sourceFile: SourceFile): boolean {
|
||||
return candidate.end > position || !isCompletedNode(candidate, sourceFile);
|
||||
}
|
||||
|
||||
function childStartsOnTheSameLineWithElseInIfStatement(parent: Node, child: Node, childStartLine: number, sourceFile: SourceFile): boolean {
|
||||
if (parent.kind === SyntaxKind.IfStatement && (<IfStatement>parent).elseStatement === child) {
|
||||
var elseKeyword = forEach(parent.getChildren(), c => c.kind === SyntaxKind.ElseKeyword && c);
|
||||
Debug.assert(elseKeyword);
|
||||
|
||||
var elseKeywordStartLine = getStartLineAndCharacterForNode(elseKeyword, sourceFile).line;
|
||||
return elseKeywordStartLine === childStartLine;
|
||||
}
|
||||
}
|
||||
|
||||
function getActualIndentationForListItem(node: Node, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
if (node.parent) {
|
||||
switch (node.parent.kind) {
|
||||
case SyntaxKind.TypeReference:
|
||||
if ((<TypeReferenceNode>node.parent).typeArguments) {
|
||||
return getActualIndentationFromList((<TypeReferenceNode>node.parent).typeArguments);
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.ObjectLiteral:
|
||||
return getActualIndentationFromList((<ObjectLiteral>node.parent).properties);
|
||||
case SyntaxKind.TypeLiteral:
|
||||
return getActualIndentationFromList((<TypeLiteralNode>node.parent).members);
|
||||
case SyntaxKind.ArrayLiteral:
|
||||
return getActualIndentationFromList((<ArrayLiteral>node.parent).elements);
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
case SyntaxKind.Method:
|
||||
case SyntaxKind.CallSignature:
|
||||
case SyntaxKind.ConstructSignature:
|
||||
if ((<SignatureDeclaration>node.parent).typeParameters && node.end < (<SignatureDeclaration>node.parent).typeParameters.end) {
|
||||
return getActualIndentationFromList((<SignatureDeclaration>node.parent).typeParameters);
|
||||
}
|
||||
|
||||
return getActualIndentationFromList((<SignatureDeclaration>node.parent).parameters);
|
||||
case SyntaxKind.NewExpression:
|
||||
case SyntaxKind.CallExpression:
|
||||
if ((<CallExpression>node.parent).typeArguments && node.end < (<CallExpression>node.parent).typeArguments.end) {
|
||||
return getActualIndentationFromList((<CallExpression>node.parent).typeArguments);
|
||||
}
|
||||
|
||||
return getActualIndentationFromList((<CallExpression>node.parent).arguments);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
||||
function getActualIndentationFromList(list: Node[]): number {
|
||||
var index = indexOf(list, node);
|
||||
return index !== -1 ? deriveActualIndentationFromList(list, index, sourceFile, options) : -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function deriveActualIndentationFromList(list: Node[], index: number, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
Debug.assert(index >= 0 && index < list.length);
|
||||
var node = list[index];
|
||||
|
||||
// walk toward the start of the list starting from current node and check if the line is the same for all items.
|
||||
// if end line for item [i - 1] differs from the start line for item [i] - find column of the first non-whitespace character on the line of item [i]
|
||||
var lineAndCharacter = getStartLineAndCharacterForNode(node, sourceFile);
|
||||
for (var i = index - 1; i >= 0; --i) {
|
||||
if (list[i].kind === SyntaxKind.CommaToken) {
|
||||
continue;
|
||||
}
|
||||
// skip list items that ends on the same line with the current list element
|
||||
var prevEndLine = sourceFile.getLineAndCharacterFromPosition(list[i].end).line;
|
||||
if (prevEndLine !== lineAndCharacter.line) {
|
||||
return findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter, sourceFile, options);
|
||||
}
|
||||
|
||||
lineAndCharacter = getStartLineAndCharacterForNode(list[i], sourceFile);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter: LineAndCharacter, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
var lineStart = sourceFile.getPositionFromLineAndCharacter(lineAndCharacter.line, 1);
|
||||
var column = 0;
|
||||
for (var i = 0; i < lineAndCharacter.character; ++i) {
|
||||
var charCode = sourceFile.text.charCodeAt(lineStart + i);
|
||||
if (!isWhiteSpace(charCode)) {
|
||||
return column;
|
||||
}
|
||||
|
||||
if (charCode === CharacterCodes.tab) {
|
||||
column += options.TabSize;
|
||||
}
|
||||
else {
|
||||
column++;
|
||||
}
|
||||
}
|
||||
|
||||
return column;
|
||||
}
|
||||
|
||||
function nodeContentIsIndented(parent: Node, child: Node): boolean {
|
||||
switch (parent.kind) {
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
return true;
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
// ModuleBlock should take care of indentation
|
||||
return false;
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.Method:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.GetAccessor:
|
||||
case SyntaxKind.SetAccessor:
|
||||
case SyntaxKind.Constructor:
|
||||
// FunctionBlock should take care of indentation
|
||||
return false;
|
||||
case SyntaxKind.DoStatement:
|
||||
case SyntaxKind.WhileStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForStatement:
|
||||
return child && child.kind !== SyntaxKind.Block;
|
||||
case SyntaxKind.IfStatement:
|
||||
return child && child.kind !== SyntaxKind.Block;
|
||||
case SyntaxKind.TryStatement:
|
||||
// TryBlock\CatchBlock\FinallyBlock should take care of indentation
|
||||
return false;
|
||||
case SyntaxKind.ArrayLiteral:
|
||||
case SyntaxKind.Block:
|
||||
case SyntaxKind.FunctionBlock:
|
||||
case SyntaxKind.TryBlock:
|
||||
case SyntaxKind.CatchBlock:
|
||||
case SyntaxKind.FinallyBlock:
|
||||
case SyntaxKind.ModuleBlock:
|
||||
case SyntaxKind.ObjectLiteral:
|
||||
case SyntaxKind.TypeLiteral:
|
||||
case SyntaxKind.SwitchStatement:
|
||||
case SyntaxKind.DefaultClause:
|
||||
case SyntaxKind.CaseClause:
|
||||
case SyntaxKind.ParenExpression:
|
||||
case SyntaxKind.CallExpression:
|
||||
case SyntaxKind.NewExpression:
|
||||
case SyntaxKind.VariableStatement:
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if node ends with 'expectedLastToken'.
|
||||
* If child at position 'length - 1' is 'SemicolonToken' it is skipped and 'expectedLastToken' is compared with child at position 'length - 2'.
|
||||
*/
|
||||
function nodeEndsWith(n: Node, expectedLastToken: SyntaxKind, sourceFile: SourceFile): boolean {
|
||||
var children = n.getChildren(sourceFile);
|
||||
if (children.length) {
|
||||
var last = children[children.length - 1];
|
||||
if (last.kind === expectedLastToken) {
|
||||
return true;
|
||||
}
|
||||
else if (last.kind === SyntaxKind.SemicolonToken && children.length !== 1) {
|
||||
return children[children.length - 2].kind === expectedLastToken;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is always called when position of the cursor is located after the node
|
||||
*/
|
||||
function isCompletedNode(n: Node, sourceFile: SourceFile): boolean {
|
||||
switch (n.kind) {
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
case SyntaxKind.ObjectLiteral:
|
||||
case SyntaxKind.Block:
|
||||
case SyntaxKind.CatchBlock:
|
||||
case SyntaxKind.FinallyBlock:
|
||||
case SyntaxKind.FunctionBlock:
|
||||
case SyntaxKind.ModuleBlock:
|
||||
case SyntaxKind.SwitchStatement:
|
||||
return nodeEndsWith(n, SyntaxKind.CloseBraceToken, sourceFile);
|
||||
case SyntaxKind.ParenExpression:
|
||||
case SyntaxKind.CallSignature:
|
||||
case SyntaxKind.CallExpression:
|
||||
case SyntaxKind.ConstructSignature:
|
||||
return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile);
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.Method:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
return !(<FunctionDeclaration>n).body || isCompletedNode((<FunctionDeclaration>n).body, sourceFile);
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
return (<ModuleDeclaration>n).body && isCompletedNode((<ModuleDeclaration>n).body, sourceFile);
|
||||
case SyntaxKind.IfStatement:
|
||||
if ((<IfStatement>n).elseStatement) {
|
||||
return isCompletedNode((<IfStatement>n).elseStatement, sourceFile);
|
||||
}
|
||||
return isCompletedNode((<IfStatement>n).thenStatement, sourceFile);
|
||||
case SyntaxKind.ExpressionStatement:
|
||||
return isCompletedNode((<ExpressionStatement>n).expression, sourceFile);
|
||||
case SyntaxKind.ArrayLiteral:
|
||||
return nodeEndsWith(n, SyntaxKind.CloseBracketToken, sourceFile);
|
||||
case SyntaxKind.Missing:
|
||||
return false;
|
||||
case SyntaxKind.CaseClause:
|
||||
case SyntaxKind.DefaultClause:
|
||||
// there is no such thing as terminator token for CaseClause\DefaultClause so for simplicitly always consider them non-completed
|
||||
return false;
|
||||
case SyntaxKind.WhileStatement:
|
||||
return isCompletedNode((<WhileStatement>n).statement, sourceFile);
|
||||
case SyntaxKind.DoStatement:
|
||||
// rough approximation: if DoStatement has While keyword - then if node is completed is checking the presence of ')';
|
||||
var hasWhileKeyword = forEach(n.getChildren(), c => c.kind === SyntaxKind.WhileKeyword && c);
|
||||
if(hasWhileKeyword) {
|
||||
return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile);
|
||||
}
|
||||
return isCompletedNode((<DoStatement>n).statement, sourceFile);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
476
src/services/formatting/new/smartIndenter.ts.orig
Normal file
476
src/services/formatting/new/smartIndenter.ts.orig
Normal file
@@ -0,0 +1,476 @@
|
||||
///<reference path='..\services.ts' />
|
||||
|
||||
module ts.formatting {
|
||||
export module SmartIndenter {
|
||||
export function getIndentation(position: number, sourceFile: SourceFile, options: TypeScript.FormattingOptions): number {
|
||||
if (position > sourceFile.text.length) {
|
||||
return 0; // past EOF
|
||||
}
|
||||
|
||||
var precedingToken = findPrecedingToken(position, sourceFile);
|
||||
if (!precedingToken) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// no indentation in string \regex literals
|
||||
if ((precedingToken.kind === SyntaxKind.StringLiteral || precedingToken.kind === SyntaxKind.RegularExpressionLiteral) &&
|
||||
precedingToken.getStart(sourceFile) <= position &&
|
||||
precedingToken.end > position) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var lineAtPosition = sourceFile.getLineAndCharacterFromPosition(position).line;
|
||||
|
||||
if (precedingToken.kind === SyntaxKind.CommaToken && precedingToken.parent.kind !== SyntaxKind.BinaryExpression) {
|
||||
|
||||
// previous token is comma that separates items in list - find the previous item and try to derive indentation from it
|
||||
var precedingListItem = findPrecedingListItem(precedingToken);
|
||||
var precedingListItemStartLineAndChar = sourceFile.getLineAndCharacterFromPosition(precedingListItem.getStart(sourceFile));
|
||||
var listStartLine = getStartLineForNode(precedingListItem.parent, sourceFile);
|
||||
|
||||
if (precedingListItemStartLineAndChar.line !== listStartLine) {
|
||||
return findFirstNonWhitespaceCharacterInLine(precedingListItemStartLineAndChar.line, precedingListItemStartLineAndChar.character, sourceFile);
|
||||
// previous list item starts on the different line with list, find first non-whitespace character in this line and use its position as indentation
|
||||
var lineStartPosition = sourceFile.getPositionFromLineAndCharacter(precedingListItemStartLineAndChar.line, 1);
|
||||
for (var i = 0; i < precedingListItemStartLineAndChar.character; ++i) {
|
||||
if (!isWhiteSpace(sourceFile.text.charCodeAt(lineStartPosition + i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// seems that this is the first non-whitespace character on the line - return it
|
||||
return precedingListItemStartLineAndChar.character;
|
||||
}
|
||||
}
|
||||
|
||||
// try to find the node that will include 'position' starting from 'precedingToken'
|
||||
// if such node is found - compute initial indentation for 'position' inside this node
|
||||
var previous: Node;
|
||||
var current = precedingToken;
|
||||
var currentStartLine: number;
|
||||
var indentation: number;
|
||||
|
||||
while (current) {
|
||||
if (isPositionBelongToNode(current, position, sourceFile)) {
|
||||
<<<<<<< HEAD
|
||||
|
||||
=======
|
||||
>>>>>>> added support for smart indentation in the middle of list items, updated test baselines
|
||||
currentStartLine = getStartLineForNode(current, sourceFile);
|
||||
|
||||
if (discardInitialIndentationIfNextTokenIsOpenOrCloseBrace(precedingToken, current, lineAtPosition, sourceFile)) {
|
||||
indentation = 0;
|
||||
}
|
||||
else {
|
||||
indentation = isNodeContentIndented(current, previous) && lineAtPosition !== currentStartLine ? options.indentSpaces : 0;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
var customIndentation = getCustomIndentationForListItem(current, sourceFile);
|
||||
if (customIndentation !== -1) {
|
||||
return customIndentation;
|
||||
}
|
||||
|
||||
// check if current node is a list item - if yes, take indentation from it
|
||||
var customIndentation = getCustomIndentationForListItem(current, sourceFile);
|
||||
if (customIndentation !== -1) {
|
||||
return customIndentation;
|
||||
}
|
||||
|
||||
previous = current;
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
if (!current) {
|
||||
// no parent was found - return 0 to be indented on the level of SourceFile
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
var parent: Node = current.parent;
|
||||
var parentStartLine: number;
|
||||
|
||||
// walk upwards and collect indentations for pairs of parent-child nodes
|
||||
// indentation is not added if parent and child nodes start on the same line or if parent is IfStatement and child starts on the same line with 'else clause'
|
||||
while (parent) {
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
|
||||
// check if current node is a list item - if yes, take indentation from it
|
||||
>>>>>>> added support for smart indentation in the middle of list items, updated test baselines
|
||||
var customIndentation = getCustomIndentationForListItem(current, sourceFile);
|
||||
if (customIndentation !== -1) {
|
||||
return customIndentation + indentation;
|
||||
}
|
||||
|
||||
parentStartLine = sourceFile.getLineAndCharacterFromPosition(parent.getStart(sourceFile)).line;
|
||||
// increase indentation if parent node wants its content to be indented and parent and child nodes don't start on the same line
|
||||
var increaseIndentation =
|
||||
isNodeContentIndented(parent, current) &&
|
||||
parentStartLine !== currentStartLine &&
|
||||
!isChildStartsOnTheSameLineWithElseInIfStatement(parent, current, currentStartLine, sourceFile);
|
||||
|
||||
if (increaseIndentation) {
|
||||
indentation += options.indentSpaces;
|
||||
}
|
||||
|
||||
current = parent;
|
||||
currentStartLine = parentStartLine;
|
||||
parent = current.parent;
|
||||
}
|
||||
|
||||
return indentation;
|
||||
}
|
||||
|
||||
function discardInitialIndentationIfNextTokenIsOpenOrCloseBrace(precedingToken: Node, current: Node, lineAtPosition: number, sourceFile: SourceFile): boolean {
|
||||
var nextToken = findNextToken(precedingToken, current);
|
||||
if (!nextToken) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nextToken.kind === SyntaxKind.OpenBraceToken) {
|
||||
// open braces are always indented at the parent level
|
||||
return true;
|
||||
}
|
||||
else if (nextToken.kind === SyntaxKind.CloseBraceToken) {
|
||||
// close braces are indented at the parent level if they are located on the same line with cursor
|
||||
// this means that if new line will be added at $ position, this case will be indented
|
||||
// class A {
|
||||
// $
|
||||
// }
|
||||
/// and this one - not
|
||||
// class A {
|
||||
// $}
|
||||
|
||||
var nextTokenStartLine = getStartLineForNode(nextToken, sourceFile);
|
||||
return lineAtPosition === nextTokenStartLine;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getStartLineForNode(n: Node, sourceFile: SourceFile): number {
|
||||
return sourceFile.getLineAndCharacterFromPosition(n.getStart(sourceFile)).line;
|
||||
}
|
||||
|
||||
function findPrecedingListItem(commaToken: Node): Node {
|
||||
// CommaToken node is synthetic and thus will be stored in SyntaxList, however parent of the CommaToken points to the container of the SyntaxList skipping the list.
|
||||
// In order to find the preceding list item we first need to locate SyntaxList itself and then search for the position of CommaToken
|
||||
var syntaxList = forEach(commaToken.parent.getChildren(), c => {
|
||||
// find syntax list that covers the span of CommaToken
|
||||
if (c.kind == SyntaxKind.SyntaxList && c.pos <= commaToken.end && c.end >= commaToken.end) {
|
||||
return c;
|
||||
}
|
||||
});
|
||||
Debug.assert(syntaxList);
|
||||
|
||||
var children = syntaxList.getChildren();
|
||||
var commaIndex = indexOf(children, commaToken);
|
||||
Debug.assert(commaIndex !== -1 && commaIndex !== 0);
|
||||
|
||||
return children[commaIndex - 1];
|
||||
}
|
||||
|
||||
function isPositionBelongToNode(candidate: Node, position: number, sourceFile: SourceFile): boolean {
|
||||
return candidate.end > position || !isCompletedNode(candidate, sourceFile);
|
||||
}
|
||||
|
||||
function isChildStartsOnTheSameLineWithElseInIfStatement(parent: Node, child: Node, childStartLine: number, sourceFile: SourceFile): boolean {
|
||||
if (parent.kind === SyntaxKind.IfStatement && (<IfStatement>parent).elseStatement === child) {
|
||||
var elseKeyword = forEach(parent.getChildren(), c => c.kind === SyntaxKind.ElseKeyword && c);
|
||||
Debug.assert(elseKeyword);
|
||||
|
||||
var elseKeywordStartLine = getStartLineForNode(elseKeyword, sourceFile);
|
||||
return elseKeywordStartLine === childStartLine;
|
||||
}
|
||||
}
|
||||
|
||||
function getCustomIndentationForListItem(node: Node, sourceFile: SourceFile): number {
|
||||
if (node.parent) {
|
||||
switch (node.parent.kind) {
|
||||
case SyntaxKind.ObjectLiteral:
|
||||
return getCustomIndentationFromList((<ObjectLiteral>node.parent).properties);
|
||||
case SyntaxKind.TypeLiteral:
|
||||
return getCustomIndentationFromList((<TypeLiteralNode>node.parent).members);
|
||||
case SyntaxKind.ArrayLiteral:
|
||||
return getCustomIndentationFromList((<ArrayLiteral>node.parent).elements);
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
case SyntaxKind.Method:
|
||||
case SyntaxKind.CallSignature:
|
||||
case SyntaxKind.ConstructSignature:
|
||||
if ((<SignatureDeclaration>node.parent).typeParameters && node.end < (<SignatureDeclaration>node.parent).typeParameters.end) {
|
||||
return getCustomIndentationFromList((<SignatureDeclaration>node.parent).typeParameters);
|
||||
}
|
||||
else {
|
||||
return getCustomIndentationFromList((<SignatureDeclaration>node.parent).parameters);
|
||||
}
|
||||
case SyntaxKind.NewExpression:
|
||||
case SyntaxKind.CallExpression:
|
||||
if ((<CallExpression>node.parent).typeArguments && node.end < (<CallExpression>node.parent).typeArguments.end) {
|
||||
return getCustomIndentationFromList((<CallExpression>node.parent).typeArguments);
|
||||
}
|
||||
else {
|
||||
return getCustomIndentationFromList((<CallExpression>node.parent).arguments);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
||||
function getCustomIndentationFromList(list: Node[]): number {
|
||||
var index = indexOf(list, node);
|
||||
if (index !== -1) {
|
||||
var lineAndCol = sourceFile.getLineAndCharacterFromPosition(node.getStart(sourceFile));
|
||||
for (var i = index - 1; i >= 0; --i) {
|
||||
var prevLineAndCol = sourceFile.getLineAndCharacterFromPosition(list[i].getStart(sourceFile));
|
||||
if (lineAndCol.line !== prevLineAndCol.line) {
|
||||
<<<<<<< HEAD
|
||||
// find the line start position
|
||||
var lineStart = sourceFile.getPositionFromLineAndCharacter(lineAndCol.line, 1);
|
||||
for (var i = 0; i <= lineAndCol.character; ++i) {
|
||||
if (!isWhiteSpace(sourceFile.text.charCodeAt(lineStart + i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
// code is unreachable because the range that we check above includes at least one non-whitespace character at the very end
|
||||
Debug.fail("Unreachable code")
|
||||
|
||||
=======
|
||||
return findFirstNonWhitespaceCharacterInLine(lineAndCol.line, lineAndCol.character, sourceFile);
|
||||
>>>>>>> added support for smart indentation in the middle of list items, updated test baselines
|
||||
}
|
||||
lineAndCol = prevLineAndCol;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
function findFirstNonWhitespaceCharacterInLine(line: number, maxCharacter: number, sourceFile: SourceFile): number {
|
||||
var lineStart = sourceFile.getPositionFromLineAndCharacter(line, 1);
|
||||
for (var i = 0; i < maxCharacter; ++i) {
|
||||
if (!isWhiteSpace(sourceFile.text.charCodeAt(lineStart + i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return maxCharacter;
|
||||
}
|
||||
|
||||
function findNextToken(previousToken: Node, parent: Node): Node {
|
||||
return find(parent);
|
||||
|
||||
function find(n: Node): Node {
|
||||
if (isToken(n) && n.pos === previousToken.end) {
|
||||
// this is token that starts at the end of previous token - return it
|
||||
return n;
|
||||
}
|
||||
|
||||
var children = n.getChildren();
|
||||
for (var i = 0, len = children.length; i < len; ++i) {
|
||||
var child = children[i];
|
||||
var shouldDiveInChildNode =
|
||||
// previous token is enclosed somewhere in the child
|
||||
(child.pos <= previousToken.pos && child.end > previousToken.end) ||
|
||||
// previous token end exactly at the beginning of child
|
||||
(child.pos === previousToken.end);
|
||||
|
||||
if (shouldDiveInChildNode && isCandidateNode(child)) {
|
||||
return find(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findPrecedingToken(position: number, sourceFile: SourceFile): Node {
|
||||
return find(sourceFile, /*diveIntoLastChild*/ false);
|
||||
|
||||
function find(n: Node, diveIntoLastChild: boolean): Node {
|
||||
if (isToken(n)) {
|
||||
return n;
|
||||
}
|
||||
|
||||
var children = n.getChildren();
|
||||
if (diveIntoLastChild) {
|
||||
var candidate = findLastChildNodeCandidate(children, /*exclusiveStartPosition*/ children.length);
|
||||
return candidate && find(candidate, diveIntoLastChild);
|
||||
}
|
||||
|
||||
for (var i = 0, len = children.length; i < len; ++i) {
|
||||
var child = children[i];
|
||||
if (isCandidateNode(child)) {
|
||||
if (position < child.end) {
|
||||
if (child.getStart(sourceFile) >= position) {
|
||||
// actual start of the node is past the position - previous token should be at the end of previous child
|
||||
var candidate = findLastChildNodeCandidate(children, /*exclusiveStartPosition*/ i);
|
||||
return candidate && find(candidate, /*diveIntoLastChild*/ true)
|
||||
}
|
||||
else {
|
||||
// candidate should be in this node
|
||||
return find(child, diveIntoLastChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// here we know that none of child token nodes embrace the position
|
||||
// try to find the closest token on the left
|
||||
if (children.length) {
|
||||
var candidate = findLastChildNodeCandidate(children, /*exclusiveStartPosition*/ children.length);
|
||||
return candidate && find(candidate, /*diveIntoLastChild*/ true);
|
||||
}
|
||||
}
|
||||
|
||||
/// finds last node that is considered as candidate for search (isCandidate(node) === true) starting from 'exclusiveStartPosition'
|
||||
function findLastChildNodeCandidate(children: Node[], exclusiveStartPosition: number): Node {
|
||||
for (var i = exclusiveStartPosition - 1; i >= 0; --i) {
|
||||
if (isCandidateNode(children[i])) {
|
||||
return children[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// checks if node is something that can contain tokens (except EOF) - filters out EOF tokens, Missing\Omitted expressions, empty SyntaxLists and expression statements that wrap any of listed nodes.
|
||||
function isCandidateNode(n: Node): boolean {
|
||||
if (n.kind === SyntaxKind.ExpressionStatement) {
|
||||
return isCandidateNode((<ExpressionStatement>n).expression);
|
||||
}
|
||||
|
||||
if (n.kind === SyntaxKind.EndOfFileToken || n.kind === SyntaxKind.OmittedExpression || n.kind === SyntaxKind.Missing) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// SyntaxList is already realized so getChildCount should be fast and non-expensive
|
||||
return n.kind !== SyntaxKind.SyntaxList || n.getChildCount() !== 0;
|
||||
}
|
||||
|
||||
function isToken(n: Node): boolean {
|
||||
return n.kind < SyntaxKind.Missing;
|
||||
}
|
||||
|
||||
function isNodeContentIndented(parent: Node, child: Node): boolean {
|
||||
switch (parent.kind) {
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
return true;
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
// ModuleBlock should take care of indentation
|
||||
return false;
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.Method:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
// FunctionBlock should take care of indentation
|
||||
return false;
|
||||
case SyntaxKind.DoStatement:
|
||||
case SyntaxKind.WhileStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForStatement:
|
||||
return child && child.kind !== SyntaxKind.Block;
|
||||
case SyntaxKind.IfStatement:
|
||||
return child && child.kind !== SyntaxKind.Block;
|
||||
case SyntaxKind.TryStatement:
|
||||
// TryBlock\CatchBlock\FinallyBlock should take care of indentation
|
||||
return false;
|
||||
case SyntaxKind.ArrayLiteral:
|
||||
case SyntaxKind.Block:
|
||||
case SyntaxKind.FunctionBlock:
|
||||
case SyntaxKind.TryBlock:
|
||||
case SyntaxKind.CatchBlock:
|
||||
case SyntaxKind.FinallyBlock:
|
||||
case SyntaxKind.ModuleBlock:
|
||||
case SyntaxKind.ObjectLiteral:
|
||||
case SyntaxKind.TypeLiteral:
|
||||
case SyntaxKind.SwitchStatement:
|
||||
case SyntaxKind.DefaultClause:
|
||||
case SyntaxKind.CaseClause:
|
||||
case SyntaxKind.ParenExpression:
|
||||
case SyntaxKind.BinaryExpression:
|
||||
case SyntaxKind.CallExpression:
|
||||
case SyntaxKind.NewExpression:
|
||||
case SyntaxKind.VariableStatement:
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// checks if node ends with 'expectedLastToken'.
|
||||
/// If child at position 'length - 1' is 'SemicolonToken' it is skipped and 'expectedLastToken' is compared with child at position 'length - 2'.
|
||||
function isNodeEndWith(n: Node, expectedLastToken: SyntaxKind, sourceFile: SourceFile): boolean {
|
||||
var children = n.getChildren(sourceFile);
|
||||
if (children.length) {
|
||||
var last = children[children.length - 1];
|
||||
if (last.kind === expectedLastToken) {
|
||||
return true;
|
||||
}
|
||||
else if (last.kind === SyntaxKind.SemicolonToken && children.length !== 1) {
|
||||
return children[children.length - 2].kind === expectedLastToken;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isCompletedNode(n: Node, sourceFile: SourceFile): boolean {
|
||||
switch (n.kind) {
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
case SyntaxKind.ObjectLiteral:
|
||||
case SyntaxKind.Block:
|
||||
case SyntaxKind.CatchBlock:
|
||||
case SyntaxKind.FinallyBlock:
|
||||
case SyntaxKind.FunctionBlock:
|
||||
case SyntaxKind.ModuleBlock:
|
||||
case SyntaxKind.SwitchStatement:
|
||||
return isNodeEndWith(n, SyntaxKind.CloseBraceToken, sourceFile);
|
||||
case SyntaxKind.ParenExpression:
|
||||
case SyntaxKind.CallSignature:
|
||||
case SyntaxKind.CallExpression:
|
||||
return isNodeEndWith(n, SyntaxKind.CloseParenToken, sourceFile);
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.Method:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
return !(<FunctionDeclaration>n).body || isCompletedNode((<FunctionDeclaration>n).body, sourceFile);
|
||||
case SyntaxKind.ModuleDeclaration:
|
||||
return (<ModuleDeclaration>n).body && isCompletedNode((<ModuleDeclaration>n).body, sourceFile);
|
||||
case SyntaxKind.IfStatement:
|
||||
if ((<IfStatement>n).elseStatement) {
|
||||
return isCompletedNode((<IfStatement>n).elseStatement, sourceFile);
|
||||
}
|
||||
return isCompletedNode((<IfStatement>n).thenStatement, sourceFile);
|
||||
case SyntaxKind.ExpressionStatement:
|
||||
return isCompletedNode((<ExpressionStatement>n).expression, sourceFile);
|
||||
case SyntaxKind.ArrayLiteral:
|
||||
return isNodeEndWith(n, SyntaxKind.CloseBracketToken, sourceFile);
|
||||
case SyntaxKind.Missing:
|
||||
return false;
|
||||
case SyntaxKind.CaseClause:
|
||||
case SyntaxKind.DefaultClause:
|
||||
// there is no such thing as terminator token for CaseClause\DefaultClause so for simplicitly always consider them non-completed
|
||||
return false;
|
||||
case SyntaxKind.VariableStatement:
|
||||
// variable statement is considered completed if it either doesn'not have variable declarations or last variable declaration is completed
|
||||
var variableDeclarations = (<VariableStatement>n).declarations;
|
||||
return variableDeclarations.length === 0 || isCompletedNode(variableDeclarations[variableDeclarations.length - 1], sourceFile);
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
// variable declaration is completed if it either doesn't have initializer or initializer is completed
|
||||
return !(<VariableDeclaration>n).initializer || isCompletedNode((<VariableDeclaration>n).initializer, sourceFile);
|
||||
case SyntaxKind.WhileStatement:
|
||||
return isCompletedNode((<WhileStatement>n).statement, sourceFile);
|
||||
case SyntaxKind.DoStatement:
|
||||
return isCompletedNode((<DoStatement>n).statement, sourceFile);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/services/formatting/new/snapshotPoint.ts
Normal file
30
src/services/formatting/new/snapshotPoint.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
module ts.formatting {
|
||||
|
||||
export class SnapshotPoint {
|
||||
constructor(public snapshot: ITextSnapshot, public position: number) {
|
||||
}
|
||||
public getContainingLine(): ITextSnapshotLine {
|
||||
return this.snapshot.getLineFromPosition(this.position);
|
||||
}
|
||||
public add(offset: number): SnapshotPoint {
|
||||
return new SnapshotPoint(this.snapshot, this.position + offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/services/formatting/new/textEditInfo.ts
Normal file
28
src/services/formatting/new/textEditInfo.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
module ts.formatting {
|
||||
export class TextEditInfo {
|
||||
|
||||
constructor(public position: number, public length: number, public replaceWith: string) {
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return "[ position: " + this.position + ", length: " + this.length + ", replaceWith: '" + this.replaceWith + "' ]";
|
||||
}
|
||||
}
|
||||
}
|
||||
89
src/services/formatting/new/textSnapshot.ts
Normal file
89
src/services/formatting/new/textSnapshot.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
module ts.formatting {
|
||||
export interface ITextSnapshot {
|
||||
getLength(): number;
|
||||
getText(span: TypeScript.TextSpan): string;
|
||||
getLineNumberFromPosition(position: number): number;
|
||||
getLineFromPosition(position: number): ITextSnapshotLine;
|
||||
getLineFromLineNumber(lineNumber: number): ITextSnapshotLine;
|
||||
}
|
||||
|
||||
export class TextSnapshot implements ITextSnapshot {
|
||||
private lines: TextSnapshotLine[];
|
||||
|
||||
constructor(private snapshot: /*ISimpleText*/ any) {
|
||||
this.lines = [];
|
||||
}
|
||||
|
||||
public getLength(): number {
|
||||
return this.snapshot.length();
|
||||
}
|
||||
|
||||
public getText(span: TypeScript.TextSpan): string {
|
||||
return this.snapshot.substr(span.start(), span.length());
|
||||
}
|
||||
|
||||
public getLineNumberFromPosition(position: number): number {
|
||||
return this.snapshot.lineMap().getLineNumberFromPosition(position);
|
||||
}
|
||||
|
||||
public getLineFromPosition(position: number): ITextSnapshotLine {
|
||||
var lineNumber = this.getLineNumberFromPosition(position);
|
||||
return this.getLineFromLineNumber(lineNumber);
|
||||
}
|
||||
|
||||
public getLineFromLineNumber(lineNumber: number): ITextSnapshotLine {
|
||||
var line = this.lines[lineNumber];
|
||||
if (line === undefined) {
|
||||
line = <TextSnapshotLine>this.getLineFromLineNumberWorker(lineNumber);
|
||||
this.lines[lineNumber] = line;
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
private getLineFromLineNumberWorker(lineNumber: number): ITextSnapshotLine {
|
||||
var lineMap = this.snapshot.lineMap().lineStarts();
|
||||
var lineMapIndex = lineNumber; //Note: lineMap is 0-based
|
||||
if (lineMapIndex < 0 || lineMapIndex >= lineMap.length)
|
||||
throw new Error(TypeScript.getDiagnosticMessage(TypeScript.DiagnosticCode.Invalid_line_number_0, [lineMapIndex]));
|
||||
var start = lineMap[lineMapIndex];
|
||||
|
||||
var end: number;
|
||||
var endIncludingLineBreak: number;
|
||||
var lineBreak = "";
|
||||
if (lineMapIndex == lineMap.length) {
|
||||
end = endIncludingLineBreak = this.snapshot.length();
|
||||
}
|
||||
else {
|
||||
endIncludingLineBreak = (lineMapIndex >= lineMap.length - 1 ? this.snapshot.length() : lineMap[lineMapIndex + 1]);
|
||||
for (var p = endIncludingLineBreak - 1; p >= start; p--) {
|
||||
var c = this.snapshot.substr(p, 1);
|
||||
//TODO: Other ones?
|
||||
if (c != "\r" && c != "\n") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
end = p + 1;
|
||||
lineBreak = this.snapshot.substr(end, endIncludingLineBreak - end);
|
||||
}
|
||||
var result = new TextSnapshotLine(this, lineNumber, start, end, lineBreak);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
80
src/services/formatting/new/textSnapshotLine.ts
Normal file
80
src/services/formatting/new/textSnapshotLine.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
module ts.formatting {
|
||||
export interface ITextSnapshotLine {
|
||||
snapshot(): ITextSnapshot;
|
||||
|
||||
start(): SnapshotPoint;
|
||||
startPosition(): number;
|
||||
|
||||
end(): SnapshotPoint;
|
||||
endPosition(): number;
|
||||
|
||||
endIncludingLineBreak(): SnapshotPoint;
|
||||
endIncludingLineBreakPosition(): number;
|
||||
|
||||
length(): number;
|
||||
lineNumber(): number;
|
||||
getText(): string;
|
||||
}
|
||||
|
||||
export class TextSnapshotLine implements ITextSnapshotLine {
|
||||
constructor(private _snapshot: ITextSnapshot, private _lineNumber: number, private _start: number, private _end: number, private _lineBreak: string) {
|
||||
}
|
||||
|
||||
public snapshot() {
|
||||
return this._snapshot;
|
||||
}
|
||||
|
||||
public start() {
|
||||
return new SnapshotPoint(this._snapshot, this._start);
|
||||
}
|
||||
|
||||
public startPosition() {
|
||||
return this._start;
|
||||
}
|
||||
|
||||
public end() {
|
||||
return new SnapshotPoint(this._snapshot, this._end);
|
||||
}
|
||||
|
||||
public endPosition() {
|
||||
return this._end;
|
||||
}
|
||||
|
||||
public endIncludingLineBreak() {
|
||||
return new SnapshotPoint(this._snapshot, this._end + this._lineBreak.length);
|
||||
}
|
||||
|
||||
public endIncludingLineBreakPosition() {
|
||||
return this._end + this._lineBreak.length;
|
||||
}
|
||||
|
||||
public length() {
|
||||
return this._end - this._start;
|
||||
}
|
||||
|
||||
public lineNumber() {
|
||||
return this._lineNumber;
|
||||
}
|
||||
|
||||
public getText(): string {
|
||||
return this._snapshot.getText(TypeScript.TextSpan.fromBounds(this._start, this._end));
|
||||
}
|
||||
}
|
||||
}
|
||||
152
src/services/formatting/new/tokenRange.ts
Normal file
152
src/services/formatting/new/tokenRange.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
module ts.formatting {
|
||||
export module Shared {
|
||||
export interface ITokenAccess {
|
||||
GetTokens(): SyntaxKind[];
|
||||
Contains(token: SyntaxKind): boolean;
|
||||
}
|
||||
|
||||
export class TokenRangeAccess implements ITokenAccess {
|
||||
private tokens: SyntaxKind[];
|
||||
|
||||
constructor(from: SyntaxKind, to: SyntaxKind, except: SyntaxKind[]) {
|
||||
this.tokens = [];
|
||||
for (var token = from; token <= to; token++) {
|
||||
if (except.indexOf(token) < 0) {
|
||||
this.tokens.push(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GetTokens(): SyntaxKind[] {
|
||||
return this.tokens;
|
||||
}
|
||||
|
||||
public Contains(token: SyntaxKind): boolean {
|
||||
return this.tokens.indexOf(token) >= 0;
|
||||
}
|
||||
|
||||
|
||||
public toString(): string {
|
||||
return "[tokenRangeStart=" + SyntaxKind[this.tokens[0]] + "," +
|
||||
"tokenRangeEnd=" + SyntaxKind[this.tokens[this.tokens.length - 1]] + "]";
|
||||
}
|
||||
}
|
||||
|
||||
export class TokenValuesAccess implements ITokenAccess {
|
||||
private tokens: SyntaxKind[];
|
||||
|
||||
constructor(tks: SyntaxKind[]) {
|
||||
this.tokens = tks && tks.length ? tks : <SyntaxKind[]>[];
|
||||
}
|
||||
|
||||
public GetTokens(): SyntaxKind[] {
|
||||
return this.tokens;
|
||||
}
|
||||
|
||||
public Contains(token: SyntaxKind): boolean {
|
||||
return this.tokens.indexOf(token) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class TokenSingleValueAccess implements ITokenAccess {
|
||||
constructor(public token: SyntaxKind) {
|
||||
}
|
||||
|
||||
public GetTokens(): SyntaxKind[] {
|
||||
return [this.token];
|
||||
}
|
||||
|
||||
public Contains(tokenValue: SyntaxKind): boolean {
|
||||
return tokenValue == this.token;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return "[singleTokenKind=" + SyntaxKind[this.token] + "]";
|
||||
}
|
||||
}
|
||||
|
||||
export class TokenAllAccess implements ITokenAccess {
|
||||
public GetTokens(): SyntaxKind[] {
|
||||
var result: SyntaxKind[] = [];
|
||||
for (var token = SyntaxKind.FirstToken; token <= SyntaxKind.LastToken; token++) {
|
||||
result.push(token);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Contains(tokenValue: SyntaxKind): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return "[allTokens]";
|
||||
}
|
||||
}
|
||||
|
||||
export class TokenRange {
|
||||
constructor(public tokenAccess: ITokenAccess) {
|
||||
}
|
||||
|
||||
static FromToken(token: SyntaxKind): TokenRange {
|
||||
return new TokenRange(new TokenSingleValueAccess(token));
|
||||
}
|
||||
|
||||
static FromTokens(tokens: SyntaxKind[]): TokenRange {
|
||||
return new TokenRange(new TokenValuesAccess(tokens));
|
||||
}
|
||||
|
||||
static FromRange(f: SyntaxKind, to: SyntaxKind, except: SyntaxKind[] = []): TokenRange {
|
||||
return new TokenRange(new TokenRangeAccess(f, to, except));
|
||||
}
|
||||
|
||||
static AllTokens(): TokenRange {
|
||||
return new TokenRange(new TokenAllAccess());
|
||||
}
|
||||
|
||||
public GetTokens(): SyntaxKind[] {
|
||||
return this.tokenAccess.GetTokens();
|
||||
}
|
||||
|
||||
public Contains(token: SyntaxKind): boolean {
|
||||
return this.tokenAccess.Contains(token);
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return this.tokenAccess.toString();
|
||||
}
|
||||
|
||||
static Any: TokenRange = TokenRange.AllTokens();
|
||||
static AnyIncludingMultilineComments = TokenRange.FromTokens(TokenRange.Any.GetTokens().concat([SyntaxKind.MultiLineCommentTrivia]));
|
||||
static Keywords = TokenRange.FromRange(SyntaxKind.FirstKeyword, SyntaxKind.LastKeyword);
|
||||
static Operators = TokenRange.FromRange(SyntaxKind.SemicolonToken, SyntaxKind.SlashEqualsToken);
|
||||
static BinaryOperators = TokenRange.FromRange(SyntaxKind.LessThanToken, SyntaxKind.SlashEqualsToken);
|
||||
static BinaryKeywordOperators = TokenRange.FromTokens([SyntaxKind.InKeyword, SyntaxKind.InstanceOfKeyword]);
|
||||
static ReservedKeywords = TokenRange.FromRange(SyntaxKind.FirstFutureReservedWord, SyntaxKind.LastFutureReservedWord);
|
||||
static UnaryPrefixOperators = TokenRange.FromTokens([SyntaxKind.PlusPlusToken, SyntaxKind.MinusMinusToken, SyntaxKind.TildeToken, SyntaxKind.ExclamationToken]);
|
||||
static UnaryPrefixExpressions = TokenRange.FromTokens([SyntaxKind.NumericLiteral, SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.OpenBracketToken, SyntaxKind.OpenBraceToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]);
|
||||
static UnaryPreincrementExpressions = TokenRange.FromTokens([SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]);
|
||||
static UnaryPostincrementExpressions = TokenRange.FromTokens([SyntaxKind.Identifier, SyntaxKind.CloseParenToken, SyntaxKind.CloseBracketToken, SyntaxKind.NewKeyword]);
|
||||
static UnaryPredecrementExpressions = TokenRange.FromTokens([SyntaxKind.Identifier, SyntaxKind.OpenParenToken, SyntaxKind.ThisKeyword, SyntaxKind.NewKeyword]);
|
||||
static UnaryPostdecrementExpressions = TokenRange.FromTokens([SyntaxKind.Identifier, SyntaxKind.CloseParenToken, SyntaxKind.CloseBracketToken, SyntaxKind.NewKeyword]);
|
||||
static Comments = TokenRange.FromTokens([SyntaxKind.SingleLineCommentTrivia, SyntaxKind.MultiLineCommentTrivia]);
|
||||
static TypeNames = TokenRange.FromTokens([SyntaxKind.Identifier, SyntaxKind.NumberKeyword, SyntaxKind.StringKeyword, SyntaxKind.BooleanKeyword, SyntaxKind.VoidKeyword, SyntaxKind.AnyKeyword]);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/services/formatting/new/tokenSpan.ts
Normal file
25
src/services/formatting/new/tokenSpan.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
///<reference path='formatting.ts' />
|
||||
|
||||
|
||||
module ts.formatting {
|
||||
export class TokenSpan extends TypeScript.TextSpan {
|
||||
constructor(public kind: SyntaxKind, start: number, length: number) {
|
||||
super(start, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
module ts.formatting {
|
||||
export module SmartIndenter {
|
||||
export function getIndentation(position: number, sourceFile: SourceFile, options: TypeScript.FormattingOptions): number {
|
||||
export function getIndentation(position: number, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
if (position > sourceFile.text.length) {
|
||||
return 0; // past EOF
|
||||
}
|
||||
@@ -44,7 +44,7 @@ module ts.formatting {
|
||||
indentationDelta = 0;
|
||||
}
|
||||
else {
|
||||
indentationDelta = lineAtPosition !== currentStart.line ? options.indentSpaces : 0;
|
||||
indentationDelta = lineAtPosition !== currentStart.line ? options.IndentSize : 0;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -91,7 +91,44 @@ module ts.formatting {
|
||||
|
||||
// increase indentation if parent node wants its content to be indented and parent and child nodes don't start on the same line
|
||||
if (nodeContentIsIndented(parent, current) && !parentAndChildShareLine) {
|
||||
indentationDelta += options.indentSpaces;
|
||||
indentationDelta += options.IndentSize;
|
||||
}
|
||||
|
||||
current = parent;
|
||||
currentStart = parentStart;
|
||||
parent = current.parent;
|
||||
}
|
||||
|
||||
return indentationDelta;
|
||||
}
|
||||
|
||||
export function getIndentationForNode(current: Node, currentStart: LineAndCharacter, indentationDelta: number, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
var parent: Node = current.parent;
|
||||
var parentStart: LineAndCharacter;
|
||||
|
||||
// walk upwards and collect indentations for pairs of parent-child nodes
|
||||
// indentation is not added if parent and child nodes start on the same line or if parent is IfStatement and child starts on the same line with 'else clause'
|
||||
while (parent) {
|
||||
// check if current node is a list item - if yes, take indentation from it
|
||||
var actualIndentation = getActualIndentationForListItem(current, sourceFile, options);
|
||||
if (actualIndentation !== -1) {
|
||||
return actualIndentation + indentationDelta;
|
||||
}
|
||||
|
||||
parentStart = sourceFile.getLineAndCharacterFromPosition(parent.getStart(sourceFile));
|
||||
var parentAndChildShareLine =
|
||||
parentStart.line === currentStart.line ||
|
||||
childStartsOnTheSameLineWithElseInIfStatement(parent, current, currentStart.line, sourceFile);
|
||||
|
||||
// try to fetch actual indentation for current node from source text
|
||||
var actualIndentation = getActualIndentationForNode(current, parent, currentStart, parentAndChildShareLine, sourceFile, options);
|
||||
if (actualIndentation !== -1) {
|
||||
return actualIndentation + indentationDelta;
|
||||
}
|
||||
|
||||
// increase indentation if parent node wants its content to be indented and parent and child nodes don't start on the same line
|
||||
if (nodeContentIsIndented(parent, current) && !parentAndChildShareLine) {
|
||||
indentationDelta += options.IndentSize;
|
||||
}
|
||||
|
||||
current = parent;
|
||||
@@ -105,7 +142,7 @@ module ts.formatting {
|
||||
/*
|
||||
* Function returns -1 if indentation cannot be determined
|
||||
*/
|
||||
function getActualIndentationForListItemBeforeComma(commaToken: Node, sourceFile: SourceFile, options: TypeScript.FormattingOptions): number {
|
||||
function getActualIndentationForListItemBeforeComma(commaToken: Node, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
// previous token is comma that separates items in list - find the previous item and try to derive indentation from it
|
||||
var commaItemInfo = findListItemInfo(commaToken);
|
||||
Debug.assert(commaItemInfo.listItemIndex > 0);
|
||||
@@ -121,7 +158,7 @@ module ts.formatting {
|
||||
currentLineAndChar: LineAndCharacter,
|
||||
parentAndChildShareLine: boolean,
|
||||
sourceFile: SourceFile,
|
||||
options: TypeScript.FormattingOptions): number {
|
||||
options: EditorOptions): number {
|
||||
|
||||
// actual indentation is used for statements\declarations if one of cases below is true:
|
||||
// - parent is SourceFile - by default immediate children of SourceFile are not indented except when user indents them manually
|
||||
@@ -172,7 +209,7 @@ module ts.formatting {
|
||||
return candidate.end > position || !isCompletedNode(candidate, sourceFile);
|
||||
}
|
||||
|
||||
function childStartsOnTheSameLineWithElseInIfStatement(parent: Node, child: Node, childStartLine: number, sourceFile: SourceFile): boolean {
|
||||
export function childStartsOnTheSameLineWithElseInIfStatement(parent: Node, child: Node, childStartLine: number, sourceFile: SourceFile): boolean {
|
||||
if (parent.kind === SyntaxKind.IfStatement && (<IfStatement>parent).elseStatement === child) {
|
||||
var elseKeyword = findChildOfKind(parent, SyntaxKind.ElseKeyword, sourceFile);
|
||||
Debug.assert(elseKeyword);
|
||||
@@ -182,7 +219,7 @@ module ts.formatting {
|
||||
}
|
||||
}
|
||||
|
||||
function getActualIndentationForListItem(node: Node, sourceFile: SourceFile, options: TypeScript.FormattingOptions): number {
|
||||
function getActualIndentationForListItem(node: Node, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
if (node.parent) {
|
||||
switch (node.parent.kind) {
|
||||
case SyntaxKind.TypeReference:
|
||||
@@ -226,7 +263,7 @@ module ts.formatting {
|
||||
}
|
||||
|
||||
|
||||
function deriveActualIndentationFromList(list: Node[], index: number, sourceFile: SourceFile, options: TypeScript.FormattingOptions): number {
|
||||
function deriveActualIndentationFromList(list: Node[], index: number, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
Debug.assert(index >= 0 && index < list.length);
|
||||
var node = list[index];
|
||||
|
||||
@@ -248,7 +285,7 @@ module ts.formatting {
|
||||
return -1;
|
||||
}
|
||||
|
||||
function findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter: LineAndCharacter, sourceFile: SourceFile, options: TypeScript.FormattingOptions): number {
|
||||
function findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter: LineAndCharacter, sourceFile: SourceFile, options: EditorOptions): number {
|
||||
var lineStart = sourceFile.getPositionFromLineAndCharacter(lineAndCharacter.line, 1);
|
||||
var column = 0;
|
||||
for (var i = 0; i < lineAndCharacter.character; ++i) {
|
||||
@@ -258,7 +295,7 @@ module ts.formatting {
|
||||
}
|
||||
|
||||
if (charCode === CharacterCodes.tab) {
|
||||
column += options.spacesPerTab;
|
||||
column += options.TabSize;
|
||||
}
|
||||
else {
|
||||
column++;
|
||||
@@ -268,7 +305,7 @@ module ts.formatting {
|
||||
return column;
|
||||
}
|
||||
|
||||
function nodeContentIsIndented(parent: Node, child: Node): boolean {
|
||||
export function nodeContentIsIndented(parent: Node, child: Node): boolean {
|
||||
switch (parent.kind) {
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
|
||||
52
src/services/formatting/stringUtilities.ts
Normal file
52
src/services/formatting/stringUtilities.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
module ts.formatting {
|
||||
|
||||
var internedTabsIndentation: string[];
|
||||
var internedSpacesIndentation: string[];
|
||||
|
||||
export function getIndentationString(indentation: number, options: FormatCodeOptions): string {
|
||||
if (!options.ConvertTabsToSpaces) {
|
||||
var tabs = Math.floor(indentation / options.TabSize);
|
||||
var spaces = indentation - tabs * options.TabSize;
|
||||
|
||||
var tabString: string;
|
||||
if (!internedTabsIndentation) {
|
||||
internedTabsIndentation = [];
|
||||
}
|
||||
|
||||
if (internedTabsIndentation[tabs] === undefined) {
|
||||
internedTabsIndentation[tabs] = tabString = repeat('\t', tabs);
|
||||
}
|
||||
else {
|
||||
tabString = internedTabsIndentation[tabs];
|
||||
}
|
||||
|
||||
return spaces ? tabString + repeat(" ", spaces) : tabString;
|
||||
}
|
||||
else {
|
||||
var spacesString: string;
|
||||
var index = indentation / options.IndentSize;
|
||||
if (!internedSpacesIndentation) {
|
||||
internedSpacesIndentation = [];
|
||||
}
|
||||
|
||||
if (internedSpacesIndentation[index] === undefined) {
|
||||
internedSpacesIndentation[index] = spacesString = repeat(" ", indentation);;
|
||||
}
|
||||
else {
|
||||
spacesString = internedSpacesIndentation[index];
|
||||
}
|
||||
|
||||
var remainder = indentation % options.IndentSize;
|
||||
return remainder ? spacesString + repeat(" ", remainder) : spacesString;
|
||||
}
|
||||
|
||||
function repeat(value: string, count: number): string {
|
||||
var s = "";
|
||||
for (var i = 0; i < count; ++i) {
|
||||
s += value;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
/// <reference path='utilities.ts' />
|
||||
/// <reference path='formatting\formatting.ts' />
|
||||
/// <reference path='formatting\smartIndenter.ts' />
|
||||
/// <reference path='formatting\format.ts' />
|
||||
|
||||
/// <reference path='core\references.ts' />
|
||||
/// <reference path='resources\references.ts' />
|
||||
@@ -640,6 +641,7 @@ module ts {
|
||||
public text: string;
|
||||
public getLineAndCharacterFromPosition(position: number): { line: number; character: number } { return null; }
|
||||
public getPositionFromLineAndCharacter(line: number, character: number): number { return -1; }
|
||||
public getLineStarts(): number[] { return undefined; }
|
||||
public amdDependencies: string[];
|
||||
public referencedFiles: FileReference[];
|
||||
public syntacticErrors: Diagnostic[];
|
||||
@@ -975,6 +977,15 @@ module ts {
|
||||
ConvertTabsToSpaces: boolean;
|
||||
}
|
||||
|
||||
export function copyEditorOptions(o: EditorOptions): EditorOptions {
|
||||
return {
|
||||
IndentSize: o.IndentSize,
|
||||
TabSize: o.TabSize,
|
||||
NewLineCharacter: o.NewLineCharacter,
|
||||
ConvertTabsToSpaces: o.ConvertTabsToSpaces
|
||||
};
|
||||
}
|
||||
|
||||
export interface FormatCodeOptions extends EditorOptions {
|
||||
InsertSpaceAfterCommaDelimiter: boolean;
|
||||
InsertSpaceAfterSemicolonInForStatements: boolean;
|
||||
@@ -986,6 +997,23 @@ module ts {
|
||||
PlaceOpenBraceOnNewLineForControlBlocks: boolean;
|
||||
}
|
||||
|
||||
export function copyFormatCodeOptions(o: FormatCodeOptions): FormatCodeOptions {
|
||||
return {
|
||||
IndentSize: o.IndentSize,
|
||||
TabSize: o.TabSize,
|
||||
NewLineCharacter: o.NewLineCharacter,
|
||||
ConvertTabsToSpaces: o.ConvertTabsToSpaces,
|
||||
InsertSpaceAfterCommaDelimiter: o.InsertSpaceAfterCommaDelimiter,
|
||||
InsertSpaceAfterSemicolonInForStatements: o.InsertSpaceAfterSemicolonInForStatements,
|
||||
InsertSpaceBeforeAndAfterBinaryOperators: o.InsertSpaceBeforeAndAfterBinaryOperators,
|
||||
InsertSpaceAfterKeywordsInControlFlowStatements: o.InsertSpaceAfterKeywordsInControlFlowStatements,
|
||||
InsertSpaceAfterFunctionKeywordForAnonymousFunctions: o.InsertSpaceAfterFunctionKeywordForAnonymousFunctions,
|
||||
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: o.InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis,
|
||||
PlaceOpenBraceOnNewLineForFunctions: o.PlaceOpenBraceOnNewLineForFunctions,
|
||||
PlaceOpenBraceOnNewLineForControlBlocks: o.PlaceOpenBraceOnNewLineForControlBlocks
|
||||
};
|
||||
}
|
||||
|
||||
export interface DefinitionInfo {
|
||||
fileName: string;
|
||||
textSpan: TypeScript.TextSpan;
|
||||
@@ -1996,6 +2024,7 @@ module ts {
|
||||
export function createLanguageService(host: LanguageServiceHost, documentRegistry: DocumentRegistry): LanguageService {
|
||||
var syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host);
|
||||
var formattingRulesProvider: TypeScript.Services.Formatting.RulesProvider;
|
||||
var ruleProvider: ts.formatting.RulesProvider;
|
||||
var hostCache: HostCache; // A cache of all the information about the files on the host side.
|
||||
var program: Program;
|
||||
|
||||
@@ -2026,6 +2055,16 @@ module ts {
|
||||
return fullTypeCheckChecker_doNotAccessDirectly || (fullTypeCheckChecker_doNotAccessDirectly = program.getTypeChecker(/*fullTypeCheck*/ true));
|
||||
}
|
||||
|
||||
function getRuleProvider(options: FormatCodeOptions) {
|
||||
// Ensure rules are initialized and up to date wrt to formatting options
|
||||
if (!ruleProvider) {
|
||||
ruleProvider = new ts.formatting.RulesProvider(host);
|
||||
}
|
||||
|
||||
ruleProvider.ensureUpToDate(options);
|
||||
return ruleProvider;
|
||||
}
|
||||
|
||||
function createCompilerHost(): CompilerHost {
|
||||
return {
|
||||
getSourceFile: (filename, languageVersion) => {
|
||||
@@ -4968,7 +5007,7 @@ module ts {
|
||||
var sourceFile = getCurrentSourceFile(filename);
|
||||
var options = new TypeScript.FormattingOptions(!editorOptions.ConvertTabsToSpaces, editorOptions.TabSize, editorOptions.IndentSize, editorOptions.NewLineCharacter)
|
||||
|
||||
return formatting.SmartIndenter.getIndentation(position, sourceFile, options);
|
||||
return formatting.SmartIndenter.getIndentation(position, sourceFile, copyEditorOptions(editorOptions));
|
||||
}
|
||||
|
||||
function getFormattingManager(filename: string, options: FormatCodeOptions) {
|
||||
@@ -4994,6 +5033,10 @@ module ts {
|
||||
|
||||
function getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions): TextChange[] {
|
||||
fileName = TypeScript.switchToForwardSlashes(fileName);
|
||||
var options = copyFormatCodeOptions(options);
|
||||
var sourceFile = getCurrentSourceFile(fileName);
|
||||
var edits = formatting.formatSelection(start, end, sourceFile, getRuleProvider(options), options);
|
||||
return edits;
|
||||
|
||||
var manager = getFormattingManager(fileName, options);
|
||||
return manager.formatSelection(start, end);
|
||||
@@ -5002,6 +5045,11 @@ module ts {
|
||||
function getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions): TextChange[] {
|
||||
fileName = TypeScript.switchToForwardSlashes(fileName);
|
||||
|
||||
var sourceFile = getCurrentSourceFile(fileName);
|
||||
var options = copyFormatCodeOptions(options)
|
||||
var edits = formatting.formatDocument(sourceFile, getRuleProvider(options), options);
|
||||
return edits;
|
||||
|
||||
var manager = getFormattingManager(fileName, options);
|
||||
return manager.formatDocument();
|
||||
}
|
||||
@@ -5011,13 +5059,25 @@ module ts {
|
||||
|
||||
var manager = getFormattingManager(fileName, options);
|
||||
|
||||
var sourceFile = getCurrentSourceFile(fileName);
|
||||
var options = copyFormatCodeOptions(options);
|
||||
|
||||
if (key === "}") {
|
||||
var edits = formatting.formatOnClosingCurly(position, sourceFile, getRuleProvider(options), options);
|
||||
return edits;
|
||||
|
||||
return manager.formatOnClosingCurlyBrace(position);
|
||||
}
|
||||
else if (key === ";") {
|
||||
var edits = formatting.formatOnSemicolon(position, sourceFile, getRuleProvider(options), options);
|
||||
return edits;
|
||||
|
||||
return manager.formatOnSemicolon(position);
|
||||
}
|
||||
else if (key === "\n") {
|
||||
var edits = formatting.formatOnEnter(position, sourceFile, getRuleProvider(options), options);
|
||||
return edits;
|
||||
|
||||
return manager.formatOnEnter(position);
|
||||
}
|
||||
|
||||
|
||||
@@ -233,6 +233,10 @@ module ts {
|
||||
return n.kind >= SyntaxKind.FirstToken && n.kind <= SyntaxKind.LastToken;
|
||||
}
|
||||
|
||||
export function isComment(kind: SyntaxKind): boolean {
|
||||
return kind === SyntaxKind.SingleLineCommentTrivia || kind === SyntaxKind.MultiLineCommentTrivia;
|
||||
}
|
||||
|
||||
function isKeyword(n: Node): boolean {
|
||||
return n.kind >= SyntaxKind.FirstKeyword && n.kind <= SyntaxKind.LastKeyword;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user