From 7f3a73b7c862441f382e07f82cb6dbc36c54ce62 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 16 Dec 2014 18:31:06 -0800 Subject: [PATCH] Change 'TextSpan' to be a simple record type with free floating functions. --- src/compiler/parser.ts | 10 +- src/compiler/types.ts | 14 +-- src/compiler/utilities.ts | 130 +++++++++++--------------- src/harness/fourslash.ts | 58 ++++++------ src/harness/harnessLanguageService.ts | 12 +-- src/services/services.ts | 18 ++-- 6 files changed, 108 insertions(+), 134 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 6173ac89fbb..46959cf435f 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -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(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); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0843b413293..0707cf9975b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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 { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index f8312a03d10..5d795b1d1ae 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -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 (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); diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 452e52d3ded..f8086d0ed52 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -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 + '.'); } diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 430dcc24477..23555411989 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -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; } diff --git a/src/services/services.ts b/src/services/services.ts index 70921f73aef..6fb64baf03f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -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 {