Skip to content

Commit

Permalink
feat: #587 - SourceFile#getLineAndColumnAtPos
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `SourceFile#getLineNumberAtPos` is now `SourceFile#getLineAndColumnAtPos`
  • Loading branch information
cancerberoSgx authored and dsherret committed Mar 31, 2019
1 parent d6946db commit b987657
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 28 deletions.
8 changes: 8 additions & 0 deletions breaking-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ These two structures are now differentiated based on their new `.kind` property.

So instead of just returning an array of nodes, it now returns a map. The key is the name it was exported on and the value is an array of declarations for that value. This will make it much easier to identify the name a node was exported on.

### Removed SourceFile#getLineNumberAtPos()

Removed `SourceFile`'s method `getLineNumberAtPos` in favor of method `getLineAndColumnAtPos` which returns an object with both line number and column number at given position:

```ts
const { line, column } = sourceFile.getLineAndColumnAtPos(position);
```

## Version 1

Renamed library to `ts-morph` and reset version to 1.0.0.
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/ast/common/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1202,7 +1202,7 @@ export class Node<NodeType extends ts.Node = ts.Node> implements TextRange {
* @param includeJsDocComments - Whether to include the JS doc comments or not.
*/
getStartLineNumber(includeJsDocComments?: boolean) {
return this._sourceFile.getLineNumberAtPos(this.getStartLinePos(includeJsDocComments));
return StringUtils.getLineNumberAtPos(this._sourceFile.getFullText(), this.getStartLinePos(includeJsDocComments));
}

/**
Expand All @@ -1211,7 +1211,7 @@ export class Node<NodeType extends ts.Node = ts.Node> implements TextRange {
getEndLineNumber() {
const sourceFileText = this._sourceFile.getFullText();
const endLinePos = getPreviousMatchingPos(sourceFileText, this.getEnd(), char => char === "\n" || char === "\r");
return this._sourceFile.getLineNumberAtPos(endLinePos);
return StringUtils.getLineNumberAtPos(this._sourceFile.getFullText(), endLinePos);
}

/**
Expand Down
12 changes: 8 additions & 4 deletions src/compiler/ast/module/SourceFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,15 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
}

/**
* Gets the line number at the provided position.
* @param pos - Position
* Gets the line and column number at the provided position (1-indexed).
* @param pos - Position.
*/
getLineNumberAtPos(pos: number) {
return StringUtils.getLineNumberAtPos(this.getFullText(), pos);
getLineAndColumnAtPos(pos: number) {
const fullText = this.getFullText();
return {
line: StringUtils.getLineNumberAtPos(fullText, pos),
column: StringUtils.getLengthFromLineStartAtPos(fullText, pos) + 1
};
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/tools/results/Diagnostic.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ProjectContext } from "../../../ProjectContext";
import { DiagnosticCategory, ts } from "../../../typescript";
import { Memoize } from "../../../utils";
import { Memoize, StringUtils } from "../../../utils";
import { SourceFile } from "../../ast";
import { DiagnosticMessageChain } from "./DiagnosticMessageChain";

Expand Down Expand Up @@ -62,7 +62,7 @@ export class Diagnostic<TCompilerObject extends ts.Diagnostic = ts.Diagnostic> {
const start = this.getStart();
if (sourceFile == null || start == null)
return undefined;
return sourceFile.getLineNumberAtPos(start);
return StringUtils.getLineNumberAtPos(sourceFile.getFullText(), start);
}

/**
Expand Down
55 changes: 54 additions & 1 deletion src/tests/compiler/ast/module/sourceFileTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1165,7 +1165,7 @@ function myFunction(param: MyClass) {

const referencing = sourceFile.getReferencingLiteralsInOtherSourceFiles();
expect(referencing.map(r => r.getText()).sort()).to.deep.equal([...[...file1.getImportDeclarations(),
...file2.getImportDeclarations(), ...file3.getExportDeclarations()].map(d => d.getModuleSpecifier()!.getText()),
...file2.getImportDeclarations(), ...file3.getExportDeclarations()].map(d => d.getModuleSpecifier()!.getText()),
`"../MyInterface"`, `"../MyInterface"`].sort());
});
});
Expand Down Expand Up @@ -1405,4 +1405,57 @@ const t = 5;`;
});
});
});

describe(nameof<SourceFile>(n => n.getLineAndColumnAtPos), () => {

function test(code: string, pos: number, expected: { line: number, column: number } | "throw") {
const { sourceFile } = getInfoFromText(code, { filePath: "/File.ts" });
if (expected === "throw") {
expect(() => sourceFile.getLineAndColumnAtPos(pos)).to.throw();
}
else {
expect(sourceFile.getLineAndColumnAtPos(pos)).to.deep.equal(expected);
}
}

it("should return line and column numbers at given position in single line source files", () => {
test(``, 0, { line: 1, column: 1 });
const code = `interface I { m1(): void }`;
test(code, 3, { line: 1, column: 4 });
test(code, 0, { line: 1, column: 1 });
test(code, code.length, { line: 1, column: code.length + 1 });
});

it("should return line and column numbers at given position in empty first line source files", () => {
const code = `\ninterface I { m1(): void }`;
test(code, 0, { line: 1, column: 1 });
test(code, 1, { line: 2, column: 1 });
test(code, code.length, { line: 2, column: code.length });
});

it("should return line and column numbers at given position in multiple lines and comments source file", () => {
const code = `
/**
* Lorem ipsum
*/
interface I {
}
// tail
`;
test(code, 0, { line: 1, column: 1 });
test(code, 1, { line: 2, column: 1 });
test(code, code.indexOf("Lorem ipsum") - 3, { line: 3, column: 1 });
test(code, code.indexOf("Lorem ipsum") + "Lorem ipsum".length, { line: 3, column: " * Lorem ipsum".length + 1 });
test(code, code.indexOf("// tail") + "// tail".length, { line: 7, column: 8 });
test(code, code.indexOf("// tail") + "// tail".length + 1, { line: 8, column: 1 });
});

it("should throw end invalid position is given", () => {
test(`var a = 1`, `var a = 1`.length + 1, "throw");
test(``, 1, "throw");
test(`interface I { m1(): void }`, 1000, "throw");
test(`\ninterface I {\n m1(): void }`, -1, "throw");
});

});
});
22 changes: 11 additions & 11 deletions src/tests/utils/stringUtilsTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ describe(nameof(StringUtils), () => {
});

it("should not throw if providing a pos the length of the string", () => {
expect(() => StringUtils.getLineNumberAtPos("", 1)).to.not.throw();
expect(() => StringUtils.getLineNumberAtPos("", 0)).to.not.throw();
});

it("should throw if providing a pos greater than the length + 1", () => {
expect(() => StringUtils.getLineNumberAtPos("", 2)).to.throw(errors.ArgumentOutOfRangeError);
expect(() => StringUtils.getLineNumberAtPos("", 1)).to.throw(errors.ArgumentOutOfRangeError);
});

function doTest(newLineType: string) {
Expand Down Expand Up @@ -50,11 +50,11 @@ describe(nameof(StringUtils), () => {
});

it("should not throw if providing a pos the length of the string", () => {
expect(() => StringUtils.getLengthFromLineStartAtPos("", 1)).to.not.throw();
expect(() => StringUtils.getLengthFromLineStartAtPos("", 0)).to.not.throw();
});

it("should throw if providing a pos greater than the length + 1", () => {
expect(() => StringUtils.getLengthFromLineStartAtPos("", 2)).to.throw(errors.ArgumentOutOfRangeError);
expect(() => StringUtils.getLengthFromLineStartAtPos("", 1)).to.throw(errors.ArgumentOutOfRangeError);
});

function doTest(text: string, pos: number, expected: number) {
Expand Down Expand Up @@ -93,11 +93,11 @@ describe(nameof(StringUtils), () => {
});

it("should not throw if providing a pos the length of the string", () => {
expect(() => StringUtils.getLineStartFromPos("", 1)).to.not.throw();
expect(() => StringUtils.getLineStartFromPos("", 0)).to.not.throw();
});

it("should throw if providing a pos greater than the length + 1", () => {
expect(() => StringUtils.getLineStartFromPos("", 2)).to.throw(errors.ArgumentOutOfRangeError);
expect(() => StringUtils.getLineStartFromPos("", 1)).to.throw(errors.ArgumentOutOfRangeError);
});

function doTest(text: string, pos: number, expected: number) {
Expand Down Expand Up @@ -136,11 +136,11 @@ describe(nameof(StringUtils), () => {
});

it("should not throw if providing a pos the length of the string", () => {
expect(() => StringUtils.getLineEndFromPos("", 1)).to.not.throw();
expect(() => StringUtils.getLineEndFromPos("", 0)).to.not.throw();
});

it("should throw if providing a pos greater than the length + 1", () => {
expect(() => StringUtils.getLineEndFromPos("", 2)).to.throw(errors.ArgumentOutOfRangeError);
expect(() => StringUtils.getLineEndFromPos("", 1)).to.throw(errors.ArgumentOutOfRangeError);
});

function doTest(text: string, pos: number, expected: number) {
Expand Down Expand Up @@ -228,15 +228,15 @@ describe(nameof(StringUtils), () => {
expect(StringUtils.insertAtLastNonWhitespace(input, insertText)).to.equal(expected);
}

it("should insert into a string that's all whitepsace", () => {
it("should insert into a string that's all whitespace", () => {
doTest(" \t\r\n \t", ",", ", \t\r\n \t");
});

it("should insert at the first nonwhitespace char", () => {
it("should insert at the first non-whitespace char", () => {
doTest(" s \t", ",", " s, \t");
});

it("should insert at the first nonwhitespace char when that's the first char", () => {
it("should insert at the first non-whitespace char when that's the first char", () => {
doTest("s \t\r\n", ",", "s, \t\r\n");
});

Expand Down
16 changes: 8 additions & 8 deletions src/utils/StringUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class StringUtils {
}

static getLineNumberAtPos(str: string, pos: number) {
errors.throwIfOutOfRange(pos, [0, str.length + 1], nameof(pos));
errors.throwIfOutOfRange(pos, [0, str.length], nameof(pos));
// do not allocate a string in this method
let count = 0;

Expand All @@ -61,12 +61,12 @@ export class StringUtils {
}

static getLengthFromLineStartAtPos(str: string, pos: number) {
errors.throwIfOutOfRange(pos, [0, str.length + 1], nameof(pos));
errors.throwIfOutOfRange(pos, [0, str.length], nameof(pos));
return pos - StringUtils.getLineStartFromPos(str, pos);
}

static getLineStartFromPos(str: string, pos: number) {
errors.throwIfOutOfRange(pos, [0, str.length + 1], nameof(pos));
errors.throwIfOutOfRange(pos, [0, str.length], nameof(pos));

while (pos > 0) {
const previousChar = str[pos - 1];
Expand All @@ -79,7 +79,7 @@ export class StringUtils {
}

static getLineEndFromPos(str: string, pos: number) {
errors.throwIfOutOfRange(pos, [0, str.length + 1], nameof(pos));
errors.throwIfOutOfRange(pos, [0, str.length], nameof(pos));

while (pos < str.length) {
const currentChar = str[pos];
Expand All @@ -96,7 +96,7 @@ export class StringUtils {
}

/**
* Escapes all the occurences of the char in the string.
* Escapes all the occurrences of the char in the string.
*/
static escapeChar(str: string, char: string) {
if (char.length !== 1)
Expand All @@ -113,7 +113,7 @@ export class StringUtils {

static indent(str: string, times: number, indentText: string, isInStringAtPos: (pos: number) => boolean) {
// todo: unit test this (right now it's somewhat tested indirectly)
const unindentRegex = times > 0 ? undefined : new RegExp(getDeindentRegexText());
const unIndentRegex = times > 0 ? undefined : new RegExp(getDeIndentRegexText());
const newLines: string[] = [];
let pos = 0;

Expand All @@ -123,14 +123,14 @@ export class StringUtils {
else if (times > 0)
newLines.push(indentText.repeat(times) + line);
else // negative
newLines.push(line.replace(unindentRegex!, ""));
newLines.push(line.replace(unIndentRegex!, ""));

pos += line.length + 1; // +1 for \n char
}

return newLines.join("\n");

function getDeindentRegexText() {
function getDeIndentRegexText() {
let text = "^";
for (let i = 0; i < Math.abs(times); i++) {
text += "(";
Expand Down

0 comments on commit b987657

Please sign in to comment.