mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-15 12:51:30 -05:00
Merge pull request #1422 from Microsoft/incrementalTests
Initial test harness for incremental parser tests.
This commit is contained in:
1
Jakefile
1
Jakefile
@@ -84,6 +84,7 @@ var harnessSources = [
|
||||
].map(function (f) {
|
||||
return path.join(harnessDirectory, f);
|
||||
}).concat([
|
||||
"incrementalParser.ts",
|
||||
"services/colorization.ts",
|
||||
"services/documentRegistry.ts",
|
||||
"services/preProcessFile.ts"
|
||||
|
||||
@@ -111,6 +111,63 @@ module Utils {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function assertInvariants(node: ts.Node, parent: ts.Node): void {
|
||||
if (node) {
|
||||
assert.isFalse(node.pos < 0, "node.pos < 0");
|
||||
assert.isFalse(node.end < 0, "node.end < 0");
|
||||
assert.isFalse(node.end < node.pos, "node.end < node.pos");
|
||||
assert.equal(node.parent, parent, "node.parent !== parent");
|
||||
|
||||
if (parent) {
|
||||
// Make sure each child is contained within the parent.
|
||||
assert.isFalse(node.pos < parent.pos, "node.pos < parent.pos");
|
||||
assert.isFalse(node.end > parent.end, "node.end > parent.end");
|
||||
}
|
||||
|
||||
ts.forEachChild(node, child => {
|
||||
assertInvariants(child, node);
|
||||
});
|
||||
|
||||
// Make sure each of the children is in order.
|
||||
var currentPos = 0;
|
||||
ts.forEachChild(node,
|
||||
child => {
|
||||
assert.isFalse(child.pos < currentPos, "child.pos < currentPos");
|
||||
currentPos = child.end;
|
||||
},
|
||||
(array: ts.NodeArray<ts.Node>) => {
|
||||
assert.isFalse(array.pos < node.pos, "array.pos < node.pos");
|
||||
assert.isFalse(array.end > node.end, "array.end > node.end");
|
||||
assert.isFalse(array.pos < currentPos, "array.pos < currentPos");
|
||||
|
||||
for (var i = 0, n = array.length; i < n; i++) {
|
||||
assert.isFalse(array[i].pos < currentPos, "array[i].pos < currentPos");
|
||||
currentPos = array[i].end
|
||||
}
|
||||
|
||||
currentPos = array.end;
|
||||
});
|
||||
|
||||
var childNodesAndArrays: any[] = [];
|
||||
ts.forEachChild(node, child => { childNodesAndArrays.push(child) }, array => { childNodesAndArrays.push(array) });
|
||||
|
||||
for (var childName in node) {
|
||||
if (childName === "parent" || childName === "nextContainer" || childName === "modifiers" || childName === "externalModuleIndicator") {
|
||||
continue;
|
||||
}
|
||||
var child = (<any>node)[childName];
|
||||
if (isNodeOrArray(child)) {
|
||||
assert.isFalse(childNodesAndArrays.indexOf(child) < 0,
|
||||
"Missing child when forEach'ing over node: " + (<any>ts).SyntaxKind[node.kind] + "-" + childName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isNodeOrArray(a: any): boolean {
|
||||
return a !== undefined && typeof a.pos === "number";
|
||||
}
|
||||
}
|
||||
|
||||
module Harness.Path {
|
||||
|
||||
@@ -21,81 +21,6 @@ class Test262BaselineRunner extends RunnerBase {
|
||||
return Test262BaselineRunner.basePath + "/" + filename;
|
||||
}
|
||||
|
||||
private static checkInvariants(node: ts.Node, parent: ts.Node): void {
|
||||
if (node) {
|
||||
if (node.pos < 0) {
|
||||
throw new Error("node.pos < 0");
|
||||
}
|
||||
if (node.end < 0) {
|
||||
throw new Error("node.end < 0");
|
||||
}
|
||||
if (node.end < node.pos) {
|
||||
throw new Error("node.end < node.pos");
|
||||
}
|
||||
if (node.parent !== parent) {
|
||||
throw new Error("node.parent !== parent");
|
||||
}
|
||||
if (parent) {
|
||||
// Make sure each child is contained within the parent.
|
||||
if (node.pos < parent.pos) {
|
||||
throw new Error("node.pos < parent.pos");
|
||||
}
|
||||
if (node.end > parent.end) {
|
||||
throw new Error("node.end > parent.end");
|
||||
}
|
||||
}
|
||||
|
||||
ts.forEachChild(node, child => {
|
||||
Test262BaselineRunner.checkInvariants(child, node);
|
||||
});
|
||||
|
||||
// Make sure each of the children is in order.
|
||||
var currentPos = 0;
|
||||
ts.forEachChild(node,
|
||||
child => {
|
||||
if (child.pos < currentPos) {
|
||||
throw new Error("child.pos < currentPos");
|
||||
}
|
||||
currentPos = child.end;
|
||||
},
|
||||
(array: ts.NodeArray<ts.Node>) => {
|
||||
if (array.pos < node.pos) {
|
||||
throw new Error("array.pos < node.pos");
|
||||
}
|
||||
if (array.end > node.end) {
|
||||
throw new Error("array.end > node.end");
|
||||
}
|
||||
|
||||
if (array.pos < currentPos) {
|
||||
throw new Error("array.pos < currentPos");
|
||||
}
|
||||
for (var i = 0, n = array.length; i < n; i++) {
|
||||
if (array[i].pos < currentPos) {
|
||||
throw new Error("array[i].pos < currentPos");
|
||||
}
|
||||
currentPos = array[i].end
|
||||
}
|
||||
|
||||
currentPos = array.end;
|
||||
});
|
||||
|
||||
var childNodesAndArrays: any[] = [];
|
||||
ts.forEachChild(node, child => { childNodesAndArrays.push(child) }, array => { childNodesAndArrays.push(array) });
|
||||
|
||||
for (var childName in node) {
|
||||
if (childName === "parent" || childName === "nextContainer" || childName === "modifiers" || childName === "externalModuleIndicator") {
|
||||
continue;
|
||||
}
|
||||
var child = (<any>node)[childName];
|
||||
if (Test262BaselineRunner.isNodeOrArray(child)) {
|
||||
if (childNodesAndArrays.indexOf(child) < 0) {
|
||||
throw new Error("Child when forEach'ing over node. " + (<any>ts).SyntaxKind[node.kind] + "-" + childName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static serializeSourceFile(file: ts.SourceFile): string {
|
||||
function getKindName(k: number): string {
|
||||
return (<any>ts).SyntaxKind[k]
|
||||
@@ -264,7 +189,7 @@ class Test262BaselineRunner extends RunnerBase {
|
||||
|
||||
it('satisfies invariants', () => {
|
||||
var sourceFile = testState.checker.getProgram().getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename));
|
||||
Test262BaselineRunner.checkInvariants(sourceFile, /*parent:*/ undefined);
|
||||
Utils.assertInvariants(sourceFile, /*parent:*/ undefined);
|
||||
});
|
||||
|
||||
it('has the expected AST',() => {
|
||||
|
||||
@@ -1679,7 +1679,7 @@ module ts {
|
||||
var scriptSnapshot = this.hostCache.getScriptSnapshot(filename);
|
||||
|
||||
var start = new Date().getTime();
|
||||
sourceFile = createSourceFileFromScriptSnapshot(filename, scriptSnapshot, getDefaultCompilerOptions(), version, /*isOpen*/ true);
|
||||
sourceFile = createLanguageServiceSourceFile(filename, scriptSnapshot, getDefaultCompilerOptions(), version, /*isOpen*/ true);
|
||||
this.host.log("SyntaxTreeCache.Initialize: createSourceFile: " + (new Date().getTime() - start));
|
||||
|
||||
var start = new Date().getTime();
|
||||
@@ -1692,7 +1692,7 @@ module ts {
|
||||
|
||||
var start = new Date().getTime();
|
||||
sourceFile = !editRange
|
||||
? createSourceFileFromScriptSnapshot(filename, scriptSnapshot, getDefaultCompilerOptions(), version, /*isOpen*/ true)
|
||||
? createLanguageServiceSourceFile(filename, scriptSnapshot, getDefaultCompilerOptions(), version, /*isOpen*/ true)
|
||||
: this.currentSourceFile.update(scriptSnapshot, version, /*isOpen*/ true, editRange);
|
||||
this.host.log("SyntaxTreeCache.Initialize: updateSourceFile: " + (new Date().getTime() - start));
|
||||
|
||||
@@ -1718,7 +1718,7 @@ module ts {
|
||||
}
|
||||
}
|
||||
|
||||
function createSourceFileFromScriptSnapshot(filename: string, scriptSnapshot: IScriptSnapshot, settings: CompilerOptions, version: string, isOpen: boolean) {
|
||||
export function createLanguageServiceSourceFile(filename: string, scriptSnapshot: IScriptSnapshot, settings: CompilerOptions, version: string, isOpen: boolean): SourceFile {
|
||||
return SourceFileObject.createSourceFileObject(filename, scriptSnapshot, settings.target, version, isOpen);
|
||||
}
|
||||
|
||||
@@ -1769,7 +1769,7 @@ module ts {
|
||||
var bucket = getBucketForCompilationSettings(compilationSettings, /*createIfMissing*/ true);
|
||||
var entry = lookUp(bucket, filename);
|
||||
if (!entry) {
|
||||
var sourceFile = createSourceFileFromScriptSnapshot(filename, scriptSnapshot, compilationSettings, version, isOpen);
|
||||
var sourceFile = createLanguageServiceSourceFile(filename, scriptSnapshot, compilationSettings, version, isOpen);
|
||||
|
||||
bucket[filename] = entry = {
|
||||
sourceFile: sourceFile,
|
||||
|
||||
731
tests/cases/unittests/incrementalParser.ts
Normal file
731
tests/cases/unittests/incrementalParser.ts
Normal file
@@ -0,0 +1,731 @@
|
||||
/// <reference path="..\..\..\src\harness\external\mocha.d.ts" />
|
||||
/// <reference path="..\..\..\src\compiler\parser.ts" />
|
||||
|
||||
module ts {
|
||||
function withChange(text: IScriptSnapshot, start: number, length: number, newText: string): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } {
|
||||
var contents = text.getText(0, text.getLength());
|
||||
var newContents = contents.substr(0, start) + newText + contents.substring(start + length);
|
||||
|
||||
return { text: ScriptSnapshot.fromString(newContents), textChangeRange: new TextChangeRange(new TextSpan(start, length), newText.length) }
|
||||
}
|
||||
|
||||
function withInsert(text: IScriptSnapshot, start: number, newText: string): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } {
|
||||
return withChange(text, start, 0, newText);
|
||||
}
|
||||
|
||||
function withDelete(text: IScriptSnapshot, start: number, length: number): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } {
|
||||
return withChange(text, start, length, "");
|
||||
}
|
||||
|
||||
function createTree(text: IScriptSnapshot, version: string) {
|
||||
var options: CompilerOptions = {};
|
||||
options.target = ScriptTarget.ES5;
|
||||
|
||||
return createLanguageServiceSourceFile(/*fileName:*/ "", text, options, version, /*isOpen:*/ true)
|
||||
}
|
||||
|
||||
// NOTE: 'reusedElements' is the expected count of elements reused from the old tree to the new
|
||||
// tree. It may change as we tweak the parser. If the count increases then that should always
|
||||
// be a good thing. If it decreases, that's not great (less reusability), but that may be
|
||||
// unavoidable. If it does decrease an investigation should be done to make sure that things
|
||||
// are still ok and we're still appropriately reusing most of the tree.
|
||||
function compareTrees(oldText: IScriptSnapshot, newText: IScriptSnapshot, textChangeRange: TextChangeRange, expectedReusedElements: number, oldTree?: SourceFile): SourceFile {
|
||||
oldTree = oldTree || createTree(oldText, /*version:*/ ".");
|
||||
Utils.assertInvariants(oldTree, /*parent:*/ undefined);
|
||||
|
||||
// Create a tree for the new text, in a non-incremental fashion.
|
||||
var newTree = createTree(newText, oldTree.version + ".");
|
||||
Utils.assertInvariants(newTree, /*parent:*/ undefined);
|
||||
|
||||
// Create a tree for the new text, in an incremental fashion.
|
||||
var incrementalNewTree = oldTree.update(newText, oldTree.version + ".", /*isOpen:*/ true, textChangeRange);
|
||||
Utils.assertInvariants(incrementalNewTree, /*parent:*/ undefined);
|
||||
|
||||
// We should get the same tree when doign a full or incremental parse.
|
||||
assertStructuralEquals(newTree, incrementalNewTree);
|
||||
|
||||
// There should be no reused nodes between two trees that are fully parsed.
|
||||
assert.isTrue(reusedElements(oldTree, newTree) === 0);
|
||||
|
||||
if (expectedReusedElements !== -1) {
|
||||
var actualReusedCount = reusedElements(oldTree, incrementalNewTree);
|
||||
assert.equal(actualReusedCount, expectedReusedElements, actualReusedCount + " !== " + expectedReusedElements);
|
||||
}
|
||||
|
||||
return incrementalNewTree;
|
||||
}
|
||||
|
||||
function assertStructuralEquals(node1: Node, node2: Node) {
|
||||
if (node1 === node2) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(node1, "node1");
|
||||
assert(node2, "node2");
|
||||
assert.equal(node1.pos, node2.pos, "node1.pos !== node2.pos");
|
||||
assert.equal(node1.end, node2.end, "node1.end !== node2.end");
|
||||
assert.equal(node1.kind, node2.kind, "node1.kind !== node2.kind");
|
||||
assert.equal(node1.flags, node2.flags, "node1.flags !== node2.flags");
|
||||
assert.equal(node1.parserContextFlags, node2.parserContextFlags, "node1.parserContextFlags !== node2.parserContextFlags");
|
||||
|
||||
forEachChild(node1,
|
||||
child1 => {
|
||||
var childName = findChildName(node1, child1);
|
||||
var child2: Node = (<any>node2)[childName];
|
||||
|
||||
assertStructuralEquals(child1, child2);
|
||||
},
|
||||
(array1: NodeArray<Node>) => {
|
||||
var childName = findChildName(node1, array1);
|
||||
var array2: NodeArray<Node> = (<any>node2)[childName];
|
||||
|
||||
assertArrayStructuralEquals(array1, array2);
|
||||
});
|
||||
}
|
||||
|
||||
function assertArrayStructuralEquals(array1: NodeArray<Node>, array2: NodeArray<Node>) {
|
||||
if (array1 === array2) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(array1, "array1");
|
||||
assert(array2, "array2");
|
||||
assert.equal(array1.pos, array2.pos, "array1.pos !== array2.pos");
|
||||
assert.equal(array1.end, array2.end, "array1.end !== array2.end");
|
||||
assert.equal(array1.length, array2.length, "array1.length !== array2.length");
|
||||
|
||||
for (var i = 0, n = array1.length; i < n; i++) {
|
||||
assertStructuralEquals(array1[i], array2[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function findChildName(parent: any, child: any) {
|
||||
for (var name in parent) {
|
||||
if (parent.hasOwnProperty(name) && parent[name] === child) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Could not find child in parent");
|
||||
}
|
||||
|
||||
function reusedElements(oldNode: SourceFile, newNode: SourceFile): number {
|
||||
var allOldElements = collectElements(oldNode);
|
||||
var allNewElements = collectElements(newNode);
|
||||
|
||||
return filter(allOldElements, v => contains(allNewElements, v)).length;
|
||||
}
|
||||
|
||||
function collectElements(node: Node) {
|
||||
var result: Node[] = [];
|
||||
visit(node);
|
||||
return result;
|
||||
|
||||
function visit(node: Node) {
|
||||
result.push(node);
|
||||
forEachChild(node, visit);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteCode(source: string, index: number, toDelete: string) {
|
||||
var repeat = toDelete.length;
|
||||
var oldTree = createTree(ScriptSnapshot.fromString(source), /*version:*/ ".");
|
||||
for (var i = 0; i < repeat; i++) {
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withDelete(oldText, index, 1);
|
||||
var newTree = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1, oldTree);
|
||||
|
||||
source = newTextAndChange.text.getText(0, newTextAndChange.text.getLength());
|
||||
oldTree = newTree;
|
||||
}
|
||||
}
|
||||
|
||||
function insertCode(source: string, index: number, toInsert: string) {
|
||||
var repeat = toInsert.length;
|
||||
var oldTree = createTree(ScriptSnapshot.fromString(source), /*version:*/ ".");
|
||||
for (var i = 0; i < repeat; i++) {
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, index + i, toInsert.charAt(i));
|
||||
var newTree = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1, oldTree);
|
||||
|
||||
source = newTextAndChange.text.getText(0, newTextAndChange.text.getLength());
|
||||
oldTree = newTree;
|
||||
}
|
||||
}
|
||||
|
||||
describe('Incremental',() => {
|
||||
it('Inserting into method',() => {
|
||||
var source = "class C {\r\n" +
|
||||
" public foo1() { }\r\n" +
|
||||
" public foo2() {\r\n" +
|
||||
" return 1;\r\n" +
|
||||
" }\r\n" +
|
||||
" public foo3() { }\r\n" +
|
||||
"}";
|
||||
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var semicolonIndex = source.indexOf(";");
|
||||
var newTextAndChange = withInsert(oldText, semicolonIndex, " + 1");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Deleting from method',() => {
|
||||
var source = "class C {\r\n" +
|
||||
" public foo1() { }\r\n" +
|
||||
" public foo2() {\r\n" +
|
||||
" return 1 + 1;\r\n" +
|
||||
" }\r\n" +
|
||||
" public foo3() { }\r\n" +
|
||||
"}";
|
||||
|
||||
var index = source.indexOf("+ 1");
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withDelete(oldText, index, "+ 1".length);
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Regular expression 1',() => {
|
||||
var source = "class C { public foo1() { /; } public foo2() { return 1;} public foo3() { } }";
|
||||
|
||||
var semicolonIndex = source.indexOf(";}");
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, semicolonIndex, "/");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Regular expression 2',() => {
|
||||
var source = "class C { public foo1() { ; } public foo2() { return 1/;} public foo3() { } }";
|
||||
|
||||
var semicolonIndex = source.indexOf(";");
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, semicolonIndex, "/");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Comment 1',() => {
|
||||
var source = "class C { public foo1() { /; } public foo2() { return 1; } public foo3() { } }";
|
||||
|
||||
var semicolonIndex = source.indexOf(";");
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, semicolonIndex, "/");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Comment 2',() => {
|
||||
var source = "class C { public foo1() { /; } public foo2() { return 1; } public foo3() { } }";
|
||||
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, 0, "//");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Comment 3',() => {
|
||||
var source = "//class C { public foo1() { /; } public foo2() { return 1; } public foo3() { } }";
|
||||
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withDelete(oldText, 0, 2);
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Comment 4',() => {
|
||||
var source = "class C { public foo1() { /; } public foo2() { */ return 1; } public foo3() { } }";
|
||||
|
||||
var index = source.indexOf(";");
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, index, "*");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Parameter 1',() => {
|
||||
// Should be able to reuse all the parameters.
|
||||
var source = "class C {\r\n" +
|
||||
" public foo2(a, b, c, d) {\r\n" +
|
||||
" return 1;\r\n" +
|
||||
" }\r\n" +
|
||||
"}";
|
||||
|
||||
var semicolonIndex = source.indexOf(";");
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, semicolonIndex, " + 1");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Type member 1',() => {
|
||||
// Should be able to reuse most of the type members.
|
||||
var source = "interface I { a: number; b: string; (c): d; new (e): f; g(): h }";
|
||||
|
||||
var index = source.indexOf(": string");
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, index, "?");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Enum element 1',() => {
|
||||
// Should be able to reuse most of the enum elements.
|
||||
var source = "enum E { a = 1, b = 1 << 1, c = 3, e = 4, f = 5, g = 7, h = 8, i = 9, j = 10 }";
|
||||
|
||||
var index = source.indexOf("<<");
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withChange(oldText, index, 2, "+");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Strict mode 1',() => {
|
||||
// In non-strict mode 'package' means nothing and can be reused. In strict mode though
|
||||
// we'll have to reparse the nodes (and generate an error for 'package();'
|
||||
//
|
||||
// Note: in this test we don't actually add 'use strict'. This is so we can compare
|
||||
// reuse with/without a strict mode change.
|
||||
var source = "foo1();\r\nfoo1();\r\nfoo1();\r\package();";
|
||||
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, 0, "'strict';\r\n");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Strict mode 2',() => {
|
||||
// In non-strict mode 'package' means nothing and can be reused. In strict mode though
|
||||
// we'll have to reparse the nodes (and generate an error for 'package();'
|
||||
var source = "foo1();\r\nfoo1();\r\nfoo1();\r\package();";
|
||||
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, 0, "'use strict';\r\n");
|
||||
|
||||
// Note the decreased reuse of nodes compared to 'Strict mode 1'
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Strict mode 3',() => {
|
||||
// In non-strict mode 'package' means nothing and can be reused. In strict mode though
|
||||
// we'll have to reparse the nodes (and generate an error for 'package();'
|
||||
//
|
||||
// Note: in this test we don't actually remove 'use strict'. This is so we can compare
|
||||
// reuse with/without a strict mode change.
|
||||
var source = "'strict';\r\nfoo1();\r\nfoo1();\r\nfoo1();\r\npackage();";
|
||||
|
||||
var index = source.indexOf('f');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withDelete(oldText, 0, index);
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Strict mode 4',() => {
|
||||
// In non-strict mode 'package' means nothing and can be reused. In strict mode though
|
||||
// we'll have to reparse the nodes (and generate an error for 'package();'
|
||||
var source = "'use strict';\r\nfoo1();\r\nfoo1();\r\nfoo1();\r\npackage();";
|
||||
|
||||
var index = source.indexOf('f');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withDelete(oldText, 0, index);
|
||||
|
||||
// Note the decreased reuse of nodes compared to testStrictMode3
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Strict mode 5',() => {
|
||||
var source = "'use blahhh';\r\nfoo1();\r\nfoo2();\r\nfoo3();\r\nfoo4();\r\nfoo4();\r\nfoo6();\r\nfoo7();\r\nfoo8();\r\nfoo9();\r\n";
|
||||
|
||||
var index = source.indexOf('b');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withChange(oldText, index, 6, "strict");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Strict mode 6',() => {
|
||||
var source = "'use strict';\r\nfoo1();\r\nfoo2();\r\nfoo3();\r\nfoo4();\r\nfoo4();\r\nfoo6();\r\nfoo7();\r\nfoo8();\r\nfoo9();\r\n";
|
||||
|
||||
var index = source.indexOf('s');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withChange(oldText, index, 6, "blahhh");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Strict mode 7',() => {
|
||||
var source = "'use blahhh';\r\nfoo1();\r\nfoo2();\r\nfoo3();\r\nfoo4();\r\nfoo4();\r\nfoo6();\r\nfoo7();\r\nfoo8();\r\nfoo9();\r\n";
|
||||
|
||||
var index = source.indexOf('f');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withDelete(oldText, 0, index);
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Parenthesized expression to arrow function 1',() => {
|
||||
var source = "var v = (a, b, c, d, e)";
|
||||
|
||||
var index = source.indexOf('a');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, index + 1, ":");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Parenthesized expression to arrow function 2',() => {
|
||||
var source = "var v = (a, b) = c";
|
||||
|
||||
var index = source.indexOf("= c") + 1;
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, index, ">");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Arrow function to parenthesized expression 1',() => {
|
||||
var source = "var v = (a:, b, c, d, e)";
|
||||
|
||||
var index = source.indexOf(':');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withDelete(oldText, index, 1);
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Arrow function to parenthesized expression 2',() => {
|
||||
var source = "var v = (a, b) => c";
|
||||
|
||||
var index = source.indexOf(">");
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withDelete(oldText, index, 1);
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Speculative generic lookahead 1',() => {
|
||||
var source = "var v = F<b>e";
|
||||
|
||||
var index = source.indexOf('b');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, index + 1, ",x");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1);
|
||||
});
|
||||
|
||||
it('Speculative generic lookahead 2',() => {
|
||||
var source = "var v = F<a,b>e";
|
||||
|
||||
var index = source.indexOf('b');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, index + 1, ",x");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1);
|
||||
});
|
||||
|
||||
it('Speculative generic lookahead 3',() => {
|
||||
var source = "var v = F<a,b,c>e";
|
||||
|
||||
var index = source.indexOf('b');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, index + 1, ",x");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1);
|
||||
});
|
||||
|
||||
it('Speculative generic lookahead 4',() => {
|
||||
var source = "var v = F<a,b,c,d>e";
|
||||
|
||||
var index = source.indexOf('b');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, index + 1, ",x");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1);
|
||||
});
|
||||
|
||||
it('Assertion to arrow function',() => {
|
||||
var source = "var v = <T>(a);";
|
||||
|
||||
var index = source.indexOf(';');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, index, " => 1");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Arrow function to assertion',() => {
|
||||
var source = "var v = <T>(a) => 1;";
|
||||
|
||||
var index = source.indexOf(' =>');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withDelete(oldText, index, " => 1".length);
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Contextual shift to shift-equals',() => {
|
||||
var source = "var v = 1 >> = 2";
|
||||
|
||||
var index = source.indexOf('>> =');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withDelete(oldText, index + 2, 1);
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Contextual shift-equals to shift',() => {
|
||||
var source = "var v = 1 >>= 2";
|
||||
|
||||
var index = source.indexOf('>>=');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, index + 2, " ");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Contextual shift to generic invocation',() => {
|
||||
var source = "var v = T>>(2)";
|
||||
|
||||
var index = source.indexOf('T');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, index, "Foo<Bar<");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Test generic invocation to contextual shift',() => {
|
||||
var source = "var v = Foo<Bar<T>>(2)";
|
||||
|
||||
var index = source.indexOf('Foo<Bar<');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withDelete(oldText, index, "Foo<Bar<".length);
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Contextual shift to generic type and initializer',() => {
|
||||
var source = "var v = T>>=2;";
|
||||
|
||||
var index = source.indexOf('=');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withChange(oldText, index, "= ".length, ": Foo<Bar<");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Generic type and initializer to contextual shift',() => {
|
||||
var source = "var v : Foo<Bar<T>>=2;";
|
||||
|
||||
var index = source.indexOf(':');
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withChange(oldText, index, ": Foo<Bar<".length, "= ");
|
||||
|
||||
// Note the decreased reuse of nodes compared to testStrictMode3
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Arithmetic operator to type argument list',() => {
|
||||
var source = "var v = new Dictionary<A, B>0";
|
||||
|
||||
var index = source.indexOf("0");
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withChange(oldText, index, 1, "()");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Type argument list to arithmetic operator',() => {
|
||||
var source = "var v = new Dictionary<A, B>()";
|
||||
|
||||
var index = source.indexOf("()");
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withDelete(oldText, index, 2);
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Yield context 1',() => {
|
||||
// We're changing from a non-generator to a genarator. We can't reuse statement nodes.
|
||||
var source = "function foo() {\r\nyield(foo1);\r\n}";
|
||||
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var index = source.indexOf("foo");
|
||||
var newTextAndChange = withInsert(oldText, index, "*");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Yield context 2',() => {
|
||||
// We're changing from a generator to a non-genarator. We can't reuse statement nodes.
|
||||
var source = "function *foo() {\r\nyield(foo1);\r\n}";
|
||||
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var index = source.indexOf("*");
|
||||
var newTextAndChange = withDelete(oldText, index, "*".length);
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Delete semicolon',() => {
|
||||
var source = "export class Foo {\r\n}\r\n\r\nexport var foo = new Foo();\r\n\r\n export function test(foo: Foo) {\r\n return true;\r\n }\r\n";
|
||||
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var index = source.lastIndexOf(";");
|
||||
var newTextAndChange = withDelete(oldText, index, 1);
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Edit after empty type parameter list',() => {
|
||||
var source = "class Dictionary<> { }\r\nvar y;\r\n";
|
||||
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var index = source.length;
|
||||
var newTextAndChange = withInsert(oldText, index, "var x;");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Delete parameter after comment',() => {
|
||||
var source = "function fn(/* comment! */ a: number, c) { }";
|
||||
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var index = source.indexOf("a:");
|
||||
var newTextAndChange = withDelete(oldText, index, "a: number,".length);
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Modifier added to accessor',() => {
|
||||
var source =
|
||||
"class C {\
|
||||
set Bar(bar:string) {}\
|
||||
}\
|
||||
var o2 = { set Foo(val:number) { } };";
|
||||
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var index = source.indexOf("set");
|
||||
var newTextAndChange = withInsert(oldText, index, "public ");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Insert parameter ahead of parameter',() => {
|
||||
var source =
|
||||
"alert(100);\
|
||||
\
|
||||
class OverloadedMonster {\
|
||||
constructor();\
|
||||
constructor(name) { }\
|
||||
}";
|
||||
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var index = source.indexOf("100");
|
||||
var newTextAndChange = withInsert(oldText, index, "'1', ");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Insert declare modifier before module',() => {
|
||||
var source =
|
||||
"module mAmbient {\
|
||||
module m3 { }\
|
||||
}";
|
||||
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var index = 0;
|
||||
var newTextAndChange = withInsert(oldText, index, "declare ");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Insert function above arrow function with comment',() => {
|
||||
var source =
|
||||
"\
|
||||
() =>\
|
||||
// do something\
|
||||
0;";
|
||||
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var index = 0;
|
||||
var newTextAndChange = withInsert(oldText, index, "function Foo() { }");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Finish incomplete regular expression',() => {
|
||||
var source = "while (true) /3; return;"
|
||||
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var index = source.length - 1;
|
||||
var newTextAndChange = withInsert(oldText, index, "/");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Regular expression to divide operation',() => {
|
||||
var source = "return;\r\nwhile (true) /3/g;"
|
||||
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var index = source.indexOf("while");
|
||||
var newTextAndChange = withDelete(oldText, index, "while ".length);
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Divide operation to regular expression',() => {
|
||||
var source = "return;\r\n(true) /3/g;"
|
||||
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var index = source.indexOf("(");
|
||||
var newTextAndChange = withInsert(oldText, index, "while ");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
it('Unterminated comment after keyword converted to identifier',() => {
|
||||
// 'public' as a keyword should be incrementally unusable (because it has an
|
||||
// unterminated comment). When we convert it to an identifier, that shouldn't
|
||||
// change anything, and we should still get the same errors.
|
||||
var source = "return; a.public /*"
|
||||
|
||||
var oldText = ScriptSnapshot.fromString(source);
|
||||
var newTextAndChange = withInsert(oldText, 0, "");
|
||||
|
||||
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
|
||||
});
|
||||
|
||||
// Simulated typing tests.
|
||||
|
||||
it('Type extends clause 1',() => {
|
||||
var source = "interface IFoo<T> { }\r\ninterface Array<T> extends IFoo<T> { }";
|
||||
|
||||
var index = source.indexOf('extends');
|
||||
deleteCode(source, index, "extends IFoo<T>");
|
||||
});
|
||||
|
||||
it('Type after incomplete enum 1',() => {
|
||||
var source = "function foo() {\r\n" +
|
||||
" function getOccurrencesAtPosition() {\r\n" +
|
||||
" switch (node) {\r\n" +
|
||||
" enum \r\n" +
|
||||
" }\r\n" +
|
||||
" \r\n" +
|
||||
" return undefined;\r\n" +
|
||||
" \r\n" +
|
||||
" function keywordToReferenceEntry() {\r\n" +
|
||||
" }\r\n" +
|
||||
" }\r\n" +
|
||||
" \r\n" +
|
||||
" return {\r\n" +
|
||||
" getEmitOutput: (filename): Bar => null,\r\n" +
|
||||
" };\r\n" +
|
||||
" }";
|
||||
|
||||
var index = source.indexOf("enum ") + "enum ".length;
|
||||
insertCode(source, index, "Fo");
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user