Change 'TextSpan' to be a simple record type with free floating functions.

This commit is contained in:
Cyrus Najmabadi
2014-12-16 18:31:06 -08:00
parent dfb1ac0f00
commit 7f3a73b7c8
6 changed files with 108 additions and 134 deletions

View File

@@ -725,7 +725,7 @@ module ts {
// The is the amount the nodes after the edit range need to be adjusted. It can be
// positive (if the edit added characters), negative (if the edit deleted characters)
// or zero (if this was a pure overwrite with nothing added/removed).
var delta = changeRange.newSpan().length() - changeRange.span().length();
var delta = changeRange.newSpan().length - changeRange.span().length;
// If we added or removed characters during the edit, then we need to go and adjust all
// the nodes after the edit. Those nodes may move forward down (if we inserted chars)
@@ -747,7 +747,7 @@ module ts {
// Also, mark any syntax elements that intersect the changed span. We know, up front,
// that we cannot reuse these elements.
updateTokenPositionsAndMarkElements(<IncrementalNode><Node>sourceFile,
changeRange.span().start(), changeRange.span().end(), changeRange.newSpan().end(), delta);
changeRange.span().start, textSpanEnd(changeRange.span()), textSpanEnd(changeRange.newSpan()), delta);
// Now that we've set up our internal incremental state just proceed and parse the
// source file in the normal fashion. When possible the parser will retrieve and
@@ -936,7 +936,7 @@ module ts {
// that the prior token sees that change.
var maxLookahead = 1;
var start = changeRange.span().start();
var start = changeRange.span().start;
// the first iteration aligns us with the change start. subsequent iteration move us to
// the left by maxLookahead tokens. We only need to do this as long as we're not at the
@@ -948,8 +948,8 @@ module ts {
start = Math.max(0, position - 1);
}
var finalSpan = createTextSpanFromBounds(start, changeRange.span().end());
var finalLength = changeRange.newLength() + (changeRange.span().start() - start);
var finalSpan = createTextSpanFromBounds(start, textSpanEnd(changeRange.span()));
var finalLength = changeRange.newLength() + (changeRange.span().start - start);
return createTextChangeRange(finalSpan, finalLength);
}

View File

@@ -1637,18 +1637,8 @@ module ts {
}
export interface TextSpan {
start(): number;
length(): number;
end(): number;
isEmpty(): boolean;
containsPosition(position: number): boolean;
containsTextSpan(span: TextSpan): boolean;
overlapsWith(span: TextSpan): boolean;
overlap(span: TextSpan): TextSpan;
intersectsWithTextSpan(span: TextSpan): boolean;
intersectsWith(start: number, length: number): boolean;
intersectsWithPosition(position: number): boolean;
intersection(span: TextSpan): TextSpan;
start: number;
length: number;
}
export interface TextChangeRange {

View File

@@ -786,78 +786,62 @@ module ts {
return false;
}
var textSpanConstructor = (function () {
function textSpanConstructor(start: number, length: number) {
if (start < 0) {
throw new Error("start < 0");
}
if (length < 0) {
throw new Error("length < 0");
}
this._start = start;
this._length = length;
export function textSpanEnd(span: TextSpan) {
return span.start + span.length
}
export function textSpanIsEmpty(span: TextSpan) {
return span.length === 0
}
export function textSpanContainsPosition(span: TextSpan, position: number) {
return position >= span.start && position < textSpanEnd(span);
}
// Returns true if 'span' contains 'other'.
export function textSpanContainsTextSpan(span: TextSpan, other: TextSpan) {
return other.start >= span.start && textSpanEnd(other) <= textSpanEnd(span);
}
export function textSpanOverlapsWith(span: TextSpan, other: TextSpan) {
var overlapStart = Math.max(span.start, other.start);
var overlapEnd = Math.min(textSpanEnd(span), textSpanEnd(other));
return overlapStart < overlapEnd;
}
export function textSpanOverlap(span1: TextSpan, span2: TextSpan) {
var overlapStart = Math.max(span1.start, span2.start);
var overlapEnd = Math.min(textSpanEnd(span1), textSpanEnd(span2));
if (overlapStart < overlapEnd) {
return createTextSpanFromBounds(overlapStart, overlapEnd);
}
return undefined;
}
textSpanConstructor.prototype = {
toJSON(key: string) {
return { start: this._start, length: this._length }
},
start() {
return this._start
},
length() {
return this._length
},
end() {
return this._start + this._length
},
isEmpty() {
return this._length === 0
},
containsPosition(position: number) {
return position >= this._start && position < this.end()
},
containsTextSpan(span: TextSpan) {
return span.start() >= this._start && span.end() <= this.end()
},
overlapsWith(span: TextSpan) {
var overlapStart = Math.max(this._start, span.start());
var overlapEnd = Math.min(this.end(), span.end());
return overlapStart < overlapEnd;
},
overlap(span: TextSpan) {
var overlapStart = Math.max(this._start, span.start());
var overlapEnd = Math.min(this.end(), span.end());
if (overlapStart < overlapEnd) {
return createTextSpanFromBounds(overlapStart, overlapEnd);
}
return undefined;
},
intersectsWithTextSpan(span: TextSpan) {
return span.start() <= this.end() && span.end() >= this._start
},
intersectsWith(start: number, length: number) {
var end = start + length;
return start <= this.end() && end >= this._start;
},
intersectsWithPosition(position: number) {
return position <= this.end() && position >= this._start;
},
intersection(span: TextSpan) {
var intersectStart = Math.max(this._start, span.start());
var intersectEnd = Math.min(this.end(), span.end());
if (intersectStart <= intersectEnd) {
return createTextSpanFromBounds(intersectStart, intersectEnd);
}
return undefined;
}
};
export function textSpanIntersectsWithTextSpan(span: TextSpan, other: TextSpan) {
return other.start <= textSpanEnd(span) && textSpanEnd(other) >= span.start
}
return textSpanConstructor;
})();
export function textSpanIntersectsWith(span: TextSpan, start: number, length: number) {
var end = start + length;
return start <= textSpanEnd(span) && end >= span.start;
}
export function textSpanIntersectsWithPosition(span: TextSpan, position: number) {
return position <= textSpanEnd(span) && position >= span.start;
}
export function textSpanIntersection(span1: TextSpan, span2: TextSpan) {
var intersectStart = Math.max(span1.start, span2.start);
var intersectEnd = Math.min(textSpanEnd(span1), textSpanEnd(span2));
if (intersectStart <= intersectEnd) {
return createTextSpanFromBounds(intersectStart, intersectEnd);
}
return undefined;
}
export function createTextSpan(start: number, length: number): TextSpan {
return new (<any>textSpanConstructor)(start, length);
return { start, length };
}
export function createTextSpanFromBounds(start: number, end: number) {
@@ -881,10 +865,10 @@ module ts {
return this._newLength;
},
newSpan() {
return createTextSpan(this.span().start(), this.newLength());
return createTextSpan(this.span().start, this.newLength());
},
isUnchanged() {
return this.span().isEmpty() && this.newLength() === 0;
return textSpanIsEmpty(this.span()) && this.newLength() === 0;
}
};
@@ -918,8 +902,8 @@ module ts {
// as it makes things much easier to reason about.
var change0 = changes[0];
var oldStartN = change0.span().start();
var oldEndN = change0.span().end();
var oldStartN = change0.span().start;
var oldEndN = textSpanEnd(change0.span());
var newEndN = oldStartN + change0.newLength();
for (var i = 1; i < changes.length; i++) {
@@ -1009,8 +993,8 @@ module ts {
var oldEnd1 = oldEndN;
var newEnd1 = newEndN;
var oldStart2 = nextChange.span().start();
var oldEnd2 = nextChange.span().end();
var oldStart2 = nextChange.span().start;
var oldEnd2 = textSpanEnd(nextChange.span());
var newEnd2 = oldStart2 + nextChange.newLength();
oldStartN = Math.min(oldStart1, oldStart2);

View File

@@ -697,7 +697,7 @@ module FourSlash {
for (var i = 0; i < references.length; i++) {
var reference = references[i];
if (reference && reference.fileName === fileName && reference.textSpan.start() === start && reference.textSpan.end() === end) {
if (reference && reference.fileName === fileName && reference.textSpan.start === start && ts.textSpanEnd(reference.textSpan) === end) {
if (typeof isWriteAccess !== "undefined" && reference.isWriteAccess !== isWriteAccess) {
this.raiseError('verifyReferencesAtPositionListContains failed - item isWriteAccess value doe not match, actual: ' + reference.isWriteAccess + ', expected: ' + isWriteAccess + '.');
}
@@ -828,17 +828,17 @@ module FourSlash {
}
ranges = ranges.sort((r1, r2) => r1.start - r2.start);
references = references.sort((r1, r2) => r1.textSpan.start() - r2.textSpan.start());
references = references.sort((r1, r2) => r1.textSpan.start - r2.textSpan.start);
for (var i = 0, n = ranges.length; i < n; i++) {
var reference = references[i];
var range = ranges[i];
if (reference.textSpan.start() !== range.start ||
reference.textSpan.end() !== range.end) {
if (reference.textSpan.start !== range.start ||
ts.textSpanEnd(reference.textSpan) !== range.end) {
this.raiseError(this.assertionMessage("Rename location",
"[" + reference.textSpan.start() + "," + reference.textSpan.end() + ")",
"[" + reference.textSpan.start + "," + ts.textSpanEnd(reference.textSpan) + ")",
"[" + range.start + "," + range.end + ")"));
}
}
@@ -978,10 +978,10 @@ module FourSlash {
}
var expectedRange = this.getRanges()[0];
if (renameInfo.triggerSpan.start() !== expectedRange.start ||
renameInfo.triggerSpan.end() !== expectedRange.end) {
if (renameInfo.triggerSpan.start !== expectedRange.start ||
ts.textSpanEnd(renameInfo.triggerSpan) !== expectedRange.end) {
this.raiseError("Expected triggerSpan [" + expectedRange.start + "," + expectedRange.end + "). Got [" +
renameInfo.triggerSpan.start() + "," + renameInfo.triggerSpan.end() + ") instead.");
renameInfo.triggerSpan.start + "," + ts.textSpanEnd(renameInfo.triggerSpan) + ") instead.");
}
}
@@ -1012,7 +1012,7 @@ module FourSlash {
private spanInfoToString(pos: number, spanInfo: ts.TextSpan, prefixString: string) {
var resultString = "SpanInfo: " + JSON.stringify(spanInfo);
if (spanInfo) {
var spanString = this.activeFile.content.substr(spanInfo.start(), spanInfo.length());
var spanString = this.activeFile.content.substr(spanInfo.start, spanInfo.length);
var spanLineMap = ts.computeLineStarts(spanString);
for (var i = 0; i < spanLineMap.length; i++) {
if (!i) {
@@ -1020,7 +1020,7 @@ module FourSlash {
}
resultString += prefixString + spanString.substring(spanLineMap[i], spanLineMap[i + 1]);
}
resultString += "\n" + prefixString + ":=> (" + this.getLineColStringAtPosition(spanInfo.start()) + ") to (" + this.getLineColStringAtPosition(spanInfo.end()) + ")";
resultString += "\n" + prefixString + ":=> (" + this.getLineColStringAtPosition(spanInfo.start) + ") to (" + this.getLineColStringAtPosition(ts.textSpanEnd(spanInfo)) + ")";
}
return resultString;
@@ -1411,14 +1411,14 @@ module FourSlash {
// We get back a set of edits, but langSvc.editScript only accepts one at a time. Use this to keep track
// of the incremental offset from each edit to the next. Assumption is that these edit ranges don't overlap
var runningOffset = 0;
edits = edits.sort((a, b) => a.span.start() - b.span.start());
edits = edits.sort((a, b) => a.span.start - b.span.start);
// Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters
var snapshot = this.languageServiceShimHost.getScriptSnapshot(fileName);
var oldContent = snapshot.getText(0, snapshot.getLength());
for (var j = 0; j < edits.length; j++) {
this.languageServiceShimHost.editScript(fileName, edits[j].span.start() + runningOffset, edits[j].span.end() + runningOffset, edits[j].newText);
this.updateMarkersForEdit(fileName, edits[j].span.start() + runningOffset, edits[j].span.end() + runningOffset, edits[j].newText);
var change = (edits[j].span.start() - edits[j].span.end()) + edits[j].newText.length;
this.languageServiceShimHost.editScript(fileName, edits[j].span.start + runningOffset, ts.textSpanEnd(edits[j].span) + runningOffset, edits[j].newText);
this.updateMarkersForEdit(fileName, edits[j].span.start + runningOffset, ts.textSpanEnd(edits[j].span) + runningOffset, edits[j].newText);
var change = (edits[j].span.start - ts.textSpanEnd(edits[j].span)) + edits[j].newText.length;
runningOffset += change;
// TODO: Consider doing this at least some of the time for higher fidelity. Currently causes a failure (bug 707150)
// this.languageService.getScriptLexicalStructure(fileName);
@@ -1495,7 +1495,7 @@ module FourSlash {
var definition = definitions[definitionIndex];
this.openFile(definition.fileName);
this.currentCaretPosition = definition.textSpan.start();
this.currentCaretPosition = definition.textSpan.start;
}
public verifyDefinitionLocationExists(negative: boolean) {
@@ -1616,7 +1616,7 @@ module FourSlash {
'\t Actual: undefined');
}
var actual = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getText(span.start(), span.end());
var actual = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getText(span.start, ts.textSpanEnd(span));
if (actual !== text) {
this.raiseError('verifyCurrentNameOrDottedNameSpanText\n' +
'\tExpected: "' + text + '"\n' +
@@ -1671,15 +1671,15 @@ module FourSlash {
if (expectedSpan) {
var expectedLength = expectedSpan.end - expectedSpan.start;
if (expectedSpan.start !== actualSpan.start() || expectedLength !== actualSpan.length()) {
if (expectedSpan.start !== actualSpan.start || expectedLength !== actualSpan.length) {
this.raiseError("verifyClassifications failed - expected span of text to be " +
"{start=" + expectedSpan.start + ", length=" + expectedLength + "}, but was " +
"{start=" + actualSpan.start() + ", length=" + actualSpan.length() + "}" +
"{start=" + actualSpan.start + ", length=" + actualSpan.length + "}" +
jsonMismatchString());
}
}
var actualText = this.activeFile.content.substr(actualSpan.start(), actualSpan.length());
var actualText = this.activeFile.content.substr(actualSpan.start, actualSpan.length);
if (expectedClassification.text !== actualText) {
this.raiseError('verifyClassifications failed - expected classified text to be ' +
expectedClassification.text + ', but was ' +
@@ -1721,8 +1721,8 @@ module FourSlash {
for (var i = 0; i < spans.length; i++) {
var expectedSpan = spans[i];
var actualSpan = actual[i];
if (expectedSpan.start !== actualSpan.textSpan.start() || expectedSpan.end !== actualSpan.textSpan.end()) {
this.raiseError('verifyOutliningSpans failed - span ' + (i + 1) + ' expected: (' + expectedSpan.start + ',' + expectedSpan.end + '), actual: (' + actualSpan.textSpan.start() + ',' + actualSpan.textSpan.end() + ')');
if (expectedSpan.start !== actualSpan.textSpan.start || expectedSpan.end !== ts.textSpanEnd(actualSpan.textSpan)) {
this.raiseError('verifyOutliningSpans failed - span ' + (i + 1) + ' expected: (' + expectedSpan.start + ',' + expectedSpan.end + '), actual: (' + actualSpan.textSpan.start + ',' + ts.textSpanEnd(actualSpan.textSpan) + ')');
}
}
}
@@ -1740,8 +1740,8 @@ module FourSlash {
var actualComment = actual[i];
var actualCommentSpan = ts.createTextSpan(actualComment.position, actualComment.message.length);
if (expectedSpan.start !== actualCommentSpan.start() || expectedSpan.end !== actualCommentSpan.end()) {
this.raiseError('verifyOutliningSpans failed - span ' + (i + 1) + ' expected: (' + expectedSpan.start + ',' + expectedSpan.end + '), actual: (' + actualCommentSpan.start() + ',' + actualCommentSpan.end() + ')');
if (expectedSpan.start !== actualCommentSpan.start || expectedSpan.end !== ts.textSpanEnd(actualCommentSpan)) {
this.raiseError('verifyOutliningSpans failed - span ' + (i + 1) + ' expected: (' + expectedSpan.start + ',' + expectedSpan.end + '), actual: (' + actualCommentSpan.start + ',' + ts.textSpanEnd(actualCommentSpan) + ')');
}
}
}
@@ -1756,12 +1756,12 @@ module FourSlash {
}
var actualMatchPosition = -1;
if (bracePosition === actual[0].start()) {
actualMatchPosition = actual[1].start();
} else if (bracePosition === actual[1].start()) {
actualMatchPosition = actual[0].start();
if (bracePosition === actual[0].start) {
actualMatchPosition = actual[1].start;
} else if (bracePosition === actual[1].start) {
actualMatchPosition = actual[0].start;
} else {
this.raiseError('verifyMatchingBracePosition failed - could not find the brace position: ' + bracePosition + ' in the returned list: (' + actual[0].start() + ',' + actual[0].end() + ') and (' + actual[1].start() + ',' + actual[1].end() + ')');
this.raiseError('verifyMatchingBracePosition failed - could not find the brace position: ' + bracePosition + ' in the returned list: (' + actual[0].start + ',' + ts.textSpanEnd(actual[0]) + ') and (' + actual[1].start + ',' + ts.textSpanEnd(actual[1]) + ')');
}
if (actualMatchPosition !== expectedMatchPosition) {
@@ -2001,7 +2001,7 @@ module FourSlash {
for (var i = 0; i < occurances.length; i++) {
var occurance = occurances[i];
if (occurance && occurance.fileName === fileName && occurance.textSpan.start() === start && occurance.textSpan.end() === end) {
if (occurance && occurance.fileName === fileName && occurance.textSpan.start === start && ts.textSpanEnd(occurance.textSpan) === end) {
if (typeof isWriteAccess !== "undefined" && occurance.isWriteAccess !== isWriteAccess) {
this.raiseError('verifyOccurancesAtPositionListContains failed - item isWriteAccess value doe not match, actual: ' + occurance.isWriteAccess + ', expected: ' + isWriteAccess + '.');
}

View File

@@ -87,7 +87,7 @@ module Harness.LanguageService {
return null;
}
return JSON.stringify({ span: { start: range.span().start(), length: range.span().length() }, newLength: range.newLength() });
return JSON.stringify({ span: { start: range.span().start, length: range.span().length }, newLength: range.newLength() });
}
}
@@ -346,9 +346,9 @@ module Harness.LanguageService {
for (var i = edits.length - 1; i >= 0; i--) {
var edit = edits[i];
var prefix = result.substring(0, edit.span.start());
var prefix = result.substring(0, edit.span.start);
var middle = edit.newText;
var suffix = result.substring(edit.span.end());
var suffix = result.substring(ts.textSpanEnd(edit.span));
result = prefix + middle + suffix;
}
return result;
@@ -367,7 +367,7 @@ module Harness.LanguageService {
}
var temp = mapEdits(edits).sort(function (a, b) {
var result = a.edit.span.start() - b.edit.span.start();
var result = a.edit.span.start - b.edit.span.start;
if (result === 0)
result = a.index - b.index;
return result;
@@ -386,7 +386,7 @@ module Harness.LanguageService {
}
var nextEdit = temp[next].edit;
var gap = nextEdit.span.start() - currentEdit.span.end();
var gap = nextEdit.span.start - ts.textSpanEnd(currentEdit.span);
// non-overlapping edits
if (gap >= 0) {
@@ -398,7 +398,7 @@ module Harness.LanguageService {
// overlapping edits: for now, we only support ignoring an next edit
// entirely contained in the current edit.
if (currentEdit.span.end() >= nextEdit.span.end()) {
if (ts.textSpanEnd(currentEdit.span) >= ts.textSpanEnd(nextEdit.span)) {
next++;
continue;
}

View File

@@ -1561,15 +1561,15 @@ module ts {
var oldText = sourceFile.scriptSnapshot;
var newText = scriptSnapshot;
Debug.assert((oldText.getLength() - textChangeRange.span().length() + textChangeRange.newLength()) === newText.getLength());
Debug.assert((oldText.getLength() - textChangeRange.span().length + textChangeRange.newLength()) === newText.getLength());
if (Debug.shouldAssert(AssertionLevel.VeryAggressive)) {
var oldTextPrefix = oldText.getText(0, textChangeRange.span().start());
var newTextPrefix = newText.getText(0, textChangeRange.span().start());
var oldTextPrefix = oldText.getText(0, textChangeRange.span().start);
var newTextPrefix = newText.getText(0, textChangeRange.span().start);
Debug.assert(oldTextPrefix === newTextPrefix);
var oldTextSuffix = oldText.getText(textChangeRange.span().end(), oldText.getLength());
var newTextSuffix = newText.getText(textChangeRange.newSpan().end(), newText.getLength());
var oldTextSuffix = oldText.getText(textSpanEnd(textChangeRange.span()), oldText.getLength());
var newTextSuffix = newText.getText(textSpanEnd(textChangeRange.newSpan()), newText.getLength());
Debug.assert(oldTextSuffix === newTextSuffix);
}
}
@@ -4852,7 +4852,7 @@ module ts {
function processNode(node: Node) {
// Only walk into nodes that intersect the requested span.
if (node && span.intersectsWith(node.getStart(), node.getWidth())) {
if (node && textSpanIntersectsWith(span, node.getStart(), node.getWidth())) {
if (node.kind === SyntaxKind.Identifier && node.getWidth() > 0) {
var symbol = typeInfoResolver.getSymbolAtLocation(node);
if (symbol) {
@@ -4883,7 +4883,7 @@ module ts {
function classifyComment(comment: CommentRange) {
var width = comment.end - comment.pos;
if (span.intersectsWith(comment.pos, width)) {
if (textSpanIntersectsWith(span, comment.pos, width)) {
result.push({
textSpan: createTextSpan(comment.pos, width),
classificationType: ClassificationTypeNames.comment
@@ -4985,7 +4985,7 @@ module ts {
function processElement(element: Node) {
// Ignore nodes that don't intersect the original span to classify.
if (span.intersectsWith(element.getFullStart(), element.getFullWidth())) {
if (textSpanIntersectsWith(span, element.getFullStart(), element.getFullWidth())) {
var children = element.getChildren();
for (var i = 0, n = children.length; i < n; i++) {
var child = children[i];
@@ -5030,7 +5030,7 @@ module ts {
var range2 = createTextSpan(current.getStart(sourceFile), current.getWidth(sourceFile));
// We want to order the braces when we return the result.
if (range1.start() < range2.start()) {
if (range1.start < range2.start) {
result.push(range1, range2);
}
else {