Skip to content

Commit

Permalink
feat: Uses the language service to figure out the indentation level w…
Browse files Browse the repository at this point in the history
…hen writing.
  • Loading branch information
dsherret committed Feb 28, 2018
1 parent 4de5f82 commit 76f9531
Show file tree
Hide file tree
Showing 26 changed files with 196 additions and 183 deletions.
3 changes: 0 additions & 3 deletions src/compiler/base/BodiedNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ export function BodiedNode<T extends Constructor<BodiedNodeExtensionType>>(Base:
return this.getNodeFromCompilerNode(body);
}

setBodyText(writerFunction: (writer: CodeBlockWriter) => void): this;
setBodyText(text: string): this;
setBodyText(textOrWriterFunction: string | ((writer: CodeBlockWriter) => void)): this;
setBodyText(textOrWriterFunction: string | ((writer: CodeBlockWriter) => void)) {
const body = this.getBody();
setBodyTextForNode(body, textOrWriterFunction);
Expand Down
6 changes: 1 addition & 5 deletions src/compiler/base/BodyableNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,6 @@ export function BodyableNode<T extends Constructor<BodyableNodeExtensionType>>(B
return this.getNodeFromCompilerNodeIfExists(this.compilerNode.body);
}

setBodyText(writerFunction: (writer: CodeBlockWriter) => void): this;
setBodyText(text: string): this;
setBodyText(textOrWriterFunction: string | ((writer: CodeBlockWriter) => void)): this;
setBodyText(textOrWriterFunction: string | ((writer: CodeBlockWriter) => void)) {
this.addBody();
setBodyTextForNode(this.getBodyOrThrow(), textOrWriterFunction);
Expand All @@ -71,12 +68,11 @@ export function BodyableNode<T extends Constructor<BodyableNodeExtensionType>>(B
return this;

const semiColon = this.getLastChildByKind(SyntaxKind.SemicolonToken);
const indentationText = this.getIndentationText();

insertIntoParentTextRange({
parent: this,
insertPos: semiColon == null ? this.getEnd() : semiColon.getStart(),
newText: this.getWriter().write(" {").newLine().write(indentationText + "}").toString(),
newText: this.getWriterWithQueuedIndentation().block().toString(),
replacing: {
textLength: semiColon == null ? 0 : semiColon.getFullWidth()
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/base/TextInsertableNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export function TextInsertableNode<T extends Constructor<TextInsertableNodeExten
insertPos: pos,
childIndex: childSyntaxList.getChildIndex(),
insertItemsCount: 1,
newText: getTextFromStringOrWriter(this.global.manipulationSettings, textOrWriterFunction),
newText: getTextFromStringOrWriter(this.getWriter(), textOrWriterFunction),
parent: this,
replacing: {
textLength: end - pos,
Expand Down
17 changes: 17 additions & 0 deletions src/compiler/base/helpers/getBodyText.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import CodeBlockWriter from "code-block-writer";
import {writeTextFromStringOrWriter} from "./../../../utils";
import {Node} from "./../../common";

/**
* @internal
*/
export function getBodyText(writer: CodeBlockWriter, textOrWriterFunction: string | ((writer: CodeBlockWriter) => void)) {
writer.newLineIfLastNotNewLine();
if (typeof textOrWriterFunction !== "string" || textOrWriterFunction.length > 0)
writer.indentBlock(() => {
writeTextFromStringOrWriter(writer, textOrWriterFunction);
});
writer.newLineIfLastNotNewLine();
writer.write(""); // write last line's indentation
return writer.toString();
}
1 change: 1 addition & 0 deletions src/compiler/base/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./getBodyText";
export * from "./setBodyTextForNode";
28 changes: 4 additions & 24 deletions src/compiler/base/helpers/setBodyTextForNode.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import {ts, SyntaxKind} from "./../../../typescript";
import CodeBlockWriter from "code-block-writer";
import {insertIntoParentTextRange, getIndentedText} from "./../../../manipulation";
import {getTextFromStringOrWriter, StringUtils} from "./../../../utils";
import {SyntaxKind} from "./../../../typescript";
import {insertIntoParentTextRange} from "./../../../manipulation";
import {Node} from "./../../common";
import {getBodyText} from "./getBodyText";

/**
* @internal
*/
export function setBodyTextForNode(body: Node, textOrWriterFunction: string | ((writer: CodeBlockWriter) => void)) {
const childSyntaxList = body.getChildSyntaxListOrThrow();
const childrenToRemove = childSyntaxList.getChildren();
const childIndentationText = body.getChildIndentationText();
const newLineKind = body.global.manipulationSettings.getNewLineKindAsString();
const newText = getNewText();
const newText = getBodyText(body.getWriterWithIndentation(), textOrWriterFunction);
const openBrace = body.getFirstChildByKindOrThrow(SyntaxKind.FirstPunctuation);
const closeBrace = body.getFirstChildByKindOrThrow(SyntaxKind.CloseBraceToken);

Expand All @@ -24,20 +20,4 @@ export function setBodyTextForNode(body: Node, textOrWriterFunction: string | ((
textLength: closeBrace.getStart() - openBrace.getEnd()
}
});

function getNewText() {
let text = getIndentedText({
textOrWriterFunction,
manipulationSettings: body.global.manipulationSettings,
indentationText: childIndentationText
});

if (text.length > 0)
text = newLineKind + text;

if (!StringUtils.endsWith(text, newLineKind))
text += newLineKind;

return text + body.getParentOrThrow().getIndentationText();
}
}
1 change: 1 addition & 0 deletions src/compiler/class/ClassDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@ export class ClassDeclaration extends ClassDeclarationBase<ts.ClassDeclaration>
const indentationText = this.getChildIndentationText();
const newLineKind = this.global.manipulationSettings.getNewLineKindAsString();
const isAmbient = this.isAmbient();
structures = structures.map(s => ({...s}));

// create code
const codes = structures.map(s => {
Expand Down
84 changes: 55 additions & 29 deletions src/compiler/common/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {IndentationText} from "./../../ManipulationSettings";
import {StructureToText} from "./../../structureToTexts";
import {insertIntoParentTextRange, getNextNonWhitespacePos, getPreviousMatchingPos, replaceSourceFileTextForFormatting,
getTextFromFormattingEdits} from "./../../manipulation";
import {TypeGuards, getTextFromStringOrWriter, ArrayUtils, isStringKind, printNode, PrintNodeOptions} from "./../../utils";
import {TypeGuards, getTextFromStringOrWriter, ArrayUtils, isStringKind, printNode, PrintNodeOptions, StringUtils} from "./../../utils";
import {SourceFile} from "./../file";
import {ConstructorDeclaration, MethodDeclaration} from "./../class";
import {FunctionDeclaration} from "./../function";
Expand Down Expand Up @@ -364,7 +364,8 @@ export class Node<NodeType extends ts.Node = ts.Node> {
TypeGuards.isBodiedNode(this) ||
TypeGuards.isCaseBlock(this) ||
TypeGuards.isCaseClause(this) ||
TypeGuards.isDefaultClause(this)
TypeGuards.isDefaultClause(this) ||
TypeGuards.isJsxElement(this)
)
return node.getFirstChildByKind(SyntaxKind.SyntaxList) as SyntaxList | undefined;

Expand Down Expand Up @@ -704,39 +705,44 @@ export class Node<NodeType extends ts.Node = ts.Node> {
}

/**
* Gets the indentation text.
* Gets the indentation level of the current node.
*/
getIndentationText(): string {
const sourceFileText = this.sourceFile.getFullText();
const startLinePos = this.getStartLinePos();
const startPos = this.getStart();
let text = "";
getIndentationLevel() {
const indentationText = this.global.manipulationSettings.getIndentationText();
return (this.global.languageService.getIdentationAtPosition(this.sourceFile, this.getStart()) / indentationText.length) + (this.sourceFile._indentOffset || 0);
}

for (let i = startPos - 1; i >= startLinePos; i--) {
const currentChar = sourceFileText[i];
switch (currentChar) {
case " ":
case "\t":
text = currentChar + text;
break;
case "\n":
return text;
default:
text = "";
}
}
/**
* Gets the child indentation level of the current node.
*/
getChildIndentationLevel(): number {
if (TypeGuards.isSourceFile(this))
return this.sourceFile._indentOffset || 0;

return this.getIndentationLevel() + 1;
}

return text;
/**
* Gets the indentation text.
* @param offset - Optional number of levels of indentation to add or remove.
*/
getIndentationText(offset = 0): string {
return this._getIndentationTextForLevel(this.getIndentationLevel() + offset);
}

/**
* Gets the next indentation level text.
* @param offset - Optional number of levels of indentation to add or remove.
*/
getChildIndentationText(): string {
if (TypeGuards.isSourceFile(this))
return "";
getChildIndentationText(offset = 0): string {
return this._getIndentationTextForLevel(this.getChildIndentationLevel() + offset);
}

return this.getIndentationText() + this.global.manipulationSettings.getIndentationText();
/**
* @internal
*/
private _getIndentationTextForLevel(level: number) {
return StringUtils.repeat(this.global.manipulationSettings.getIndentationText(), level);
}

/**
Expand Down Expand Up @@ -802,7 +808,7 @@ export class Node<NodeType extends ts.Node = ts.Node> {
*/
replaceWithText(text: string): Node;
replaceWithText(textOrWriterFunction: string | ((writer: CodeBlockWriter) => void)) {
const newText = getTextFromStringOrWriter(this.global.manipulationSettings, textOrWriterFunction);
const newText = getTextFromStringOrWriter(this.getWriter(), textOrWriterFunction);
if (TypeGuards.isSourceFile(this)) {
this.replaceText([this.getPos(), this.getEnd()], newText);
return this;
Expand Down Expand Up @@ -1176,7 +1182,17 @@ export class Node<NodeType extends ts.Node = ts.Node> {
*/
getWriterWithIndentation() {
const writer = this.getWriter();
writer.setIndentationLevel(this.getIndentationText());
writer.setIndentationLevel(this.getIndentationLevel());
return writer;
}

/**
* Gets a writer with the queued indentation text.
* @internal
*/
getWriterWithQueuedIndentation() {
const writer = this.getWriter();
writer.queueIndentationLevel(this.getIndentationLevel());
return writer;
}

Expand All @@ -1186,7 +1202,17 @@ export class Node<NodeType extends ts.Node = ts.Node> {
*/
getWriterWithChildIndentation() {
const writer = this.getWriter();
writer.setIndentationLevel(this.getChildIndentationText());
writer.setIndentationLevel(this.getChildIndentationLevel());
return writer;
}

/**
* Gets a writer with the queued child indentation text.
* @internal
*/
getWriterWithQueuedChildIndentation() {
const writer = this.getWriter();
writer.queueIndentationLevel(this.getChildIndentationLevel());
return writer;
}

Expand Down
10 changes: 3 additions & 7 deletions src/compiler/common/SyntaxList.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {ts} from "./../../typescript";
import CodeBlockWriter from "code-block-writer";
import {verifyAndGetIndex, getIndentedText, getInsertPosFromIndex, insertIntoParentTextRange} from "./../../manipulation";
import {TypeGuards, StringUtils} from "./../../utils";
import {verifyAndGetIndex, getInsertPosFromIndex, insertIntoParentTextRange} from "./../../manipulation";
import {TypeGuards, StringUtils, getTextFromStringOrWriter} from "./../../utils";
import * as errors from "./../../errors";
import {Node} from "./Node";

Expand Down Expand Up @@ -52,11 +52,7 @@ export class SyntaxList extends Node<ts.SyntaxList> {
index = verifyAndGetIndex(index, initialChildCount);

// get text
let insertText = getIndentedText({
textOrWriterFunction,
manipulationSettings: this.global.manipulationSettings,
indentationText: parent.getChildIndentationText()
});
let insertText = getTextFromStringOrWriter(parent.getWriterWithChildIndentation(), textOrWriterFunction);

if (insertText.length === 0)
return [];
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/doc/JSDoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class JSDoc extends Node<ts.JSDoc> {
const endEditPos = tags.length > 0 ? getPreviousMatchingPos(this.sourceFile.getFullText(), tags[0].getStart(), c => c === "*") - 1 : this.getEnd() - 2;
const indentationText = this.getIndentationText();
const newLineKind = this.global.manipulationSettings.getNewLineKindAsString();
const text = getTextFromStringOrWriter(this.global.manipulationSettings, textOrWriterFunction);
const text = getTextFromStringOrWriter(this.getWriter(), textOrWriterFunction);
const newText = newLineKind + text.split(/\r?\n/).map(l => `${indentationText} * ${l}`).join(newLineKind) + newLineKind + indentationText + " ";

replaceTextPossiblyCreatingChildNodes({
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/file/SourceFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export const SourceFileBase: Constructor<StatementedNode> & Constructor<TextInse
export class SourceFile extends SourceFileBase<ts.SourceFile> {
/** @internal */
private _isSaved = false;
/** @internal */
_indentOffset: number | undefined; // todo: remove eventually

/**
* Initializes a new instance.
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/statement/StatementedNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -921,8 +921,10 @@ export function StatementedNode<T extends Constructor<StatementedNodeExtensionTy

// insert into a temp file
const finalChildCodes: string[] = [];
const childIndentLevel = TypeGuards.isSourceFile(this) ? 0 : (this.getIndentationLevel() + 1);
for (let i = 0; i < childCodes.length; i++) {
const tempSourceFile = this.global.compilerFactory.createTempSourceFileFromText(childCodes[i], { createLanguageService: true });
tempSourceFile._indentOffset = childIndentLevel;
if (withEachChild != null) {
const tempSyntaxList = tempSourceFile.getChildSyntaxListOrThrow();
withEachChild(tempSyntaxList.getChildren()[0] as U, i);
Expand Down
19 changes: 0 additions & 19 deletions src/manipulation/code/getIndentedText.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/manipulation/code/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from "./getIndentedText";
export * from "./getNewInsertCode";
2 changes: 1 addition & 1 deletion src/manipulation/manipulations/insertion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export function insertIntoCommaSeparatedNodes(opts: InsertIntoCommaSeparatedNode
}
else {
if (opts.useNewLines)
newText = separator + newText + parent.global.manipulationSettings.getNewLineKindAsString() + parent.getIndentationText();
newText = separator + newText + parent.global.manipulationSettings.getNewLineKindAsString() + parent.getParentOrThrow().getIndentationText();

insertIntoParentTextRange({
insertPos: parent.getPos(),
Expand Down
15 changes: 8 additions & 7 deletions src/tests/compiler/base/decoratableNodeTests.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {expect} from "chai";
import {DecoratorStructure, DecoratableNodeStructure} from "./../../../structures";
import {DecoratableNode, Decorator, ClassDeclaration} from "./../../../compiler";
import {getInfoFromText} from "./../testHelpers";
import {SyntaxKind} from "./../../../typescript";
import {getInfoFromText, getInfoFromTextWithDescendant} from "./../testHelpers";

describe(nameof(DecoratableNode), () => {
describe(nameof<DecoratableNode>(d => d.getDecorator), () => {
Expand Down Expand Up @@ -54,8 +55,8 @@ describe(nameof(DecoratableNode), () => {
describe(nameof<DecoratableNode>(n => n.insertDecorators), () => {
describe("class decorators", () => {
function doTest(startCode: string, index: number, structures: DecoratorStructure[], expectedCode: string) {
const {firstChild, sourceFile} = getInfoFromText<ClassDeclaration>(startCode);
const result = firstChild.insertDecorators(index, structures);
const {descendant, sourceFile} = getInfoFromTextWithDescendant<ClassDeclaration>(startCode, SyntaxKind.ClassDeclaration);
const result = descendant.insertDecorators(index, structures);
expect(result.length).to.equal(structures.length);
expect(sourceFile.getFullText()).to.equal(expectedCode);
}
Expand All @@ -70,7 +71,7 @@ describe(nameof(DecoratableNode), () => {
});

it("should insert on the same indentation level", () => {
doTest(" class MyClass {}", 0, [{ name: "dec" }, { name: "dec2" }], " @dec\n @dec2\n class MyClass {}");
doTest("namespace N {\n class MyClass {}\n}", 0, [{ name: "dec" }, { name: "dec2" }], "namespace N {\n @dec\n @dec2\n class MyClass {}\n}");
});

it("should insert at the start", () => {
Expand All @@ -82,13 +83,13 @@ describe(nameof(DecoratableNode), () => {
});

it("should insert one in the middle at the same indentation", () => {
doTest(" @dec\n @dec3\nclass MyClass {}", 1, [{ name: "dec2" }], " @dec\n @dec2\n @dec3\nclass MyClass {}");
doTest("namespace N {\n @dec\n @dec3\nclass MyClass {}\n}", 1, [{ name: "dec2" }], "namespace N {\n @dec\n @dec2\n @dec3\nclass MyClass {}\n}");
});

it("should insert multiple in the middle at the same indentation", () => {
doTest(
" @dec\n @dec5\nclass MyClass {}", 1, [{ name: "dec2" }, { name: "dec3" }, { name: "dec4" }],
" @dec\n @dec2\n @dec3\n @dec4\n @dec5\nclass MyClass {}"
"namespace N {\n @dec\n @dec5\nclass MyClass {}\n}", 1, [{ name: "dec2" }, { name: "dec3" }, { name: "dec4" }],
"namespace N {\n @dec\n @dec2\n @dec3\n @dec4\n @dec5\nclass MyClass {}\n}"
);
});

Expand Down

0 comments on commit 76f9531

Please sign in to comment.