diff --git a/src/harness/unittests/extractConstants.ts b/src/harness/unittests/extractConstants.ts
index e2c64afb526..d37f9b2036c 100644
--- a/src/harness/unittests/extractConstants.ts
+++ b/src/harness/unittests/extractConstants.ts
@@ -40,7 +40,7 @@ namespace ts {
}
}`);
- testExtractConstant("extractConstant_ClassInsertionPosition",
+ testExtractConstant("extractConstant_ClassInsertionPosition1",
`class C {
a = 1;
b = 2;
@@ -51,6 +51,28 @@ namespace ts {
}
}`);
+ testExtractConstant("extractConstant_ClassInsertionPosition2",
+ `class C {
+ a = 1;
+ M1() { }
+ b = 2;
+ M2() { }
+ M3() {
+ let x = [#|1|];
+ }
+}`);
+
+ testExtractConstant("extractConstant_ClassInsertionPosition3",
+ `class C {
+ M1() { }
+ a = 1;
+ b = 2;
+ M2() { }
+ M3() {
+ let x = [#|1|];
+ }
+}`);
+
testExtractConstant("extractConstant_Parameters",
`function F() {
let w = 1;
@@ -62,7 +84,7 @@ namespace ts {
let x = [#|t + 1|];
}`);
-// TODO (acasey): handle repeated substitution
+// TODO (18857): handle repeated substitution
// testExtractConstant("extractConstant_RepeatedSubstitution",
// `namespace X {
// export const j = 10;
@@ -75,6 +97,121 @@ namespace ts {
let x = [#|i + 1|];
}
}`);
+
+ testExtractConstant("extractConstant_VariableList_const",
+ `const a = 1, b = [#|a + 1|];`);
+
+ // NOTE: this test isn't normative - it just documents our sub-optimal behavior.
+ testExtractConstant("extractConstant_VariableList_let",
+ `let a = 1, b = [#|a + 1|];`);
+
+ // NOTE: this test isn't normative - it just documents our sub-optimal behavior.
+ testExtractConstant("extractConstant_VariableList_MultipleLines",
+ `const /*About A*/a = 1,
+ /*About B*/b = [#|a + 1|];`);
+
+ // NOTE: this test isn't normative - it just documents our sub-optimal behavior.
+ // `i` doesn't bind in the target scope (file-level), so the extraction is disallowed.
+ // TODO (17098): should probably allow extraction into the same scope
+ testExtractConstantFailed("extractConstant_BlockScopeMismatch", `
+for (let i = 0; i < 10; i++) {
+ for (let j = 0; j < 10; j++) {
+ const x = [#|i + 1|];
+ }
+}
+ `);
+
+ testExtractConstant("extractConstant_StatementInsertionPosition1", `
+const i = 0;
+for (let j = 0; j < 10; j++) {
+ const x = [#|i + 1|];
+}
+ `);
+
+ testExtractConstant("extractConstant_StatementInsertionPosition2", `
+const i = 0;
+function F() {
+ for (let j = 0; j < 10; j++) {
+ const x = [#|i + 1|];
+ }
+}
+ `);
+
+ testExtractConstant("extractConstant_StatementInsertionPosition3", `
+for (let j = 0; j < 10; j++) {
+ const x = [#|2 + 1|];
+}
+ `);
+
+ testExtractConstant("extractConstant_StatementInsertionPosition4", `
+function F() {
+ for (let j = 0; j < 10; j++) {
+ const x = [#|2 + 1|];
+ }
+}
+ `);
+
+ testExtractConstant("extractConstant_StatementInsertionPosition5", `
+function F0() {
+ function F1() {
+ function F2(x = [#|2 + 1|]) {
+ }
+ }
+}
+ `);
+
+ testExtractConstant("extractConstant_StatementInsertionPosition6", `
+class C {
+ x = [#|2 + 1|];
+}
+ `);
+
+ testExtractConstant("extractConstant_StatementInsertionPosition7", `
+const i = 0;
+class C {
+ M() {
+ for (let j = 0; j < 10; j++) {
+ x = [#|i + 1|];
+ }
+ }
+}
+ `);
+
+ testExtractConstant("extractConstant_TripleSlash", `
+///
+
+const x = [#|2 + 1|];
+ `);
+
+ testExtractConstant("extractConstant_PinnedComment", `
+/*! Copyright */
+
+const x = [#|2 + 1|];
+ `);
+
+ testExtractConstant("extractConstant_Directive", `
+"strict";
+
+const x = [#|2 + 1|];
+ `);
+
+ testExtractConstant("extractConstant_MultipleHeaders", `
+/*! Copyright */
+
+///
+
+"strict";
+
+const x = [#|2 + 1|];
+ `);
+
+ // NOTE: this test isn't normative - it just documents our sub-optimal behavior.
+ testExtractConstant("extractConstant_PinnedCommentAndDocComment", `
+/*! Copyright */
+
+/* About x */
+const x = [#|2 + 1|];
+ `);
});
function testExtractConstant(caption: string, text: string) {
diff --git a/src/harness/unittests/extractFunctions.ts b/src/harness/unittests/extractFunctions.ts
index 522ea7b1293..c4cbadacdc3 100644
--- a/src/harness/unittests/extractFunctions.ts
+++ b/src/harness/unittests/extractFunctions.ts
@@ -365,7 +365,7 @@ function parsePrimaryExpression(): any {
[#|function G() { }|]
}`);
-// TODO (acasey): handle repeated substitution
+// TODO (18857): handle repeated substitution
// testExtractFunction("extractFunction_RepeatedSubstitution",
// `namespace X {
// export const j = 10;
diff --git a/src/harness/unittests/extractTestHelpers.ts b/src/harness/unittests/extractTestHelpers.ts
index ea1cccd32ea..0d739919e1f 100644
--- a/src/harness/unittests/extractTestHelpers.ts
+++ b/src/harness/unittests/extractTestHelpers.ts
@@ -113,7 +113,7 @@ namespace ts {
if (hasSyntacticDiagnostics(program)) {
// Don't bother generating JS baselines for inputs that aren't valid JS.
- assert.equal(Extension.Js, extension);
+ assert.equal(Extension.Js, extension, "Syntactic diagnostics found in non-JS file");
return;
}
diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts
index 375acb7e585..ad4da2c61fc 100644
--- a/src/services/refactors/extractSymbol.ts
+++ b/src/services/refactors/extractSymbol.ts
@@ -890,33 +890,56 @@ namespace ts.refactor.extractSymbol {
createIdentifier(localNameText));
// Declare
- const minInsertionPos = node.end;
- const nodeToInsertBefore = getNodeToInsertConstantBefore(minInsertionPos, scope);
+ const maxInsertionPos = node.pos;
+ const nodeToInsertBefore = getNodeToInsertPropertyBefore(maxInsertionPos, scope);
changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariable, { suffix: context.newLineCharacter + context.newLineCharacter });
// Consume
changeTracker.replaceNodeWithNodes(context.file, node, [localReference], { nodeSeparator: context.newLineCharacter });
}
else {
- const newVariable = createVariableStatement(
- /*modifiers*/ undefined,
- createVariableDeclarationList(
- [createVariableDeclaration(localNameText, variableType, initializer)],
- NodeFlags.Const));
+ const newVariableDeclaration = createVariableDeclaration(localNameText, variableType, initializer);
- // If the parent is an expression statement, replace the statement with the declaration
- if (node.parent.kind === SyntaxKind.ExpressionStatement) {
- changeTracker.replaceNodeWithNodes(context.file, node.parent, [newVariable], { nodeSeparator: context.newLineCharacter });
- }
- else {
+ // If the node is part of an initializer in a list of variable declarations, insert a new
+ // variable declaration into the list (in case it depends on earlier ones).
+ // CONSIDER: If the declaration list isn't const, we might want to split it into multiple
+ // lists so that the newly extracted one can be const.
+ const oldVariableDeclaration = getContainingVariableDeclarationIfInList(node, scope);
+ if (oldVariableDeclaration) {
// Declare
- const minInsertionPos = node.end;
- const nodeToInsertBefore = getNodeToInsertConstantBefore(minInsertionPos, scope);
- changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariable, { suffix: context.newLineCharacter + context.newLineCharacter });
+ // CONSIDER: could detect that each is on a separate line
+ changeTracker.insertNodeAt(context.file, oldVariableDeclaration.getStart(), newVariableDeclaration, { suffix: ", " });
// Consume
const localReference = createIdentifier(localNameText);
- changeTracker.replaceNodeWithNodes(context.file, node, [localReference], { nodeSeparator: context.newLineCharacter });
+ changeTracker.replaceRange(context.file, { pos: node.getStart(), end: node.end }, localReference);
+ }
+ else if (node.parent.kind === SyntaxKind.ExpressionStatement) {
+ // If the parent is an expression statement, replace the statement with the declaration.
+ const newVariableStatement = createVariableStatement(
+ /*modifiers*/ undefined,
+ createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const));
+ changeTracker.replaceNodeWithNodes(context.file, node.parent, [newVariableStatement], { nodeSeparator: context.newLineCharacter });
+ }
+ else {
+ const newVariableStatement = createVariableStatement(
+ /*modifiers*/ undefined,
+ createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const));
+
+ // Declare
+ const nodeToInsertBefore = getNodeToInsertConstantBefore(node, scope);
+ if (nodeToInsertBefore.pos === 0) {
+ // If we're at the beginning of the file, we need to take care not to insert before header comments
+ // (e.g. copyright, triple-slash references).
+ changeTracker.insertNodeAt(context.file, nodeToInsertBefore.getStart(), newVariableStatement, { suffix: context.newLineCharacter + context.newLineCharacter });
+ }
+ else {
+ changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariableStatement, { suffix: context.newLineCharacter + context.newLineCharacter });
+ }
+
+ // Consume
+ const localReference = createIdentifier(localNameText);
+ changeTracker.replaceRange(context.file, { pos: node.getStart(), end: node.end }, localReference);
}
}
@@ -927,6 +950,22 @@ namespace ts.refactor.extractSymbol {
return { renameFilename, renameLocation, edits };
}
+ function getContainingVariableDeclarationIfInList(node: Node, scope: Scope) {
+ let prevNode = undefined;
+ while (node !== undefined && node !== scope) {
+ if (isVariableDeclaration(node) &&
+ node.initializer === prevNode &&
+ isVariableDeclarationList(node.parent) &&
+ node.parent.declarations.length > 1) {
+
+ return node;
+ }
+
+ prevNode = node;
+ node = node.parent;
+ }
+ }
+
/**
* @return The index of the (only) reference to the extracted symbol. We want the cursor
* to be on the reference, rather than the declaration, because it's closer to where the
@@ -1109,26 +1148,61 @@ namespace ts.refactor.extractSymbol {
child.pos >= minPos && isFunctionLikeDeclaration(child) && !isConstructorDeclaration(child));
}
- // TODO (acasey): need to dig into nested statements
- // TODO (acasey): don't insert before pinned comments, directives, or triple-slash references
- function getNodeToInsertConstantBefore(maxPos: number, scope: Scope): Node {
- const children = getStatementsOrClassElements(scope);
- Debug.assert(children.length > 0); // There must be at least one child, since we extracted from one.
+ function getNodeToInsertPropertyBefore(maxPos: number, scope: ClassLikeDeclaration): Node {
+ const members = scope.members;
+ Debug.assert(members.length > 0); // There must be at least one child, since we extracted from one.
- const isClassLikeScope = isClassLike(scope);
- let prevChild: Statement | ClassElement | undefined = undefined;
- for (const child of children) {
- if (child.pos >= maxPos) {
- break;
+ let prevMember: ClassElement | undefined = undefined;
+ let allProperties = true;
+ for (const member of members) {
+ if (member.pos > maxPos) {
+ return prevMember || members[0];
}
- prevChild = child;
- if (isClassLikeScope && !isPropertyDeclaration(child)) {
- break;
+ if (allProperties && !isPropertyDeclaration(member)) {
+ // If it is non-vacuously true that all preceding members are properties,
+ // insert before the current member (i.e. at the end of the list of properties).
+ if (prevMember !== undefined) {
+ return member;
+ }
+
+ allProperties = false;
+ }
+ prevMember = member;
+ }
+
+ Debug.assert(prevMember !== undefined); // If the loop didn't return, then it did set prevMember.
+ return prevMember;
+ }
+
+ function getNodeToInsertConstantBefore(node: Node, scope: Scope): Node {
+ Debug.assert(!isClassLike(scope));
+
+ let prevScope: Scope | undefined = undefined;
+ for (let curr = node; curr !== scope; curr = curr.parent) {
+ if (isScope(curr)) {
+ prevScope = curr;
}
}
- Debug.assert(prevChild !== undefined);
- return prevChild;
+ for (let curr = (prevScope || node).parent; ; curr = curr.parent) {
+ if (isBlockLike(curr)) {
+ let prevStatement = undefined;
+ for (const statement of curr.statements) {
+ if (statement.pos > node.pos) {
+ break;
+ }
+ prevStatement = statement;
+ }
+ // There must be at least one statement since we started in one.
+ Debug.assert(prevStatement !== undefined);
+ return prevStatement;
+ }
+
+ if (curr === scope) {
+ Debug.fail("Didn't encounter a block-like before encountering scope");
+ break;
+ }
+ }
}
function getPropertyAssignmentsForWrites(writes: ReadonlyArray): ShorthandPropertyAssignment[] {
diff --git a/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.js b/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.js
index 25609a3a801..b934af59550 100644
--- a/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.js
+++ b/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.js
@@ -5,10 +5,10 @@ for (let i = 0; i < 10; i++) {
}
}
// ==SCOPE::Extract to constant in global scope==
-const newLocal = 1;
-
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
+ const newLocal = 1;
+
let x = /*RENAME*/newLocal;
}
}
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.ts b/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.ts
index 25609a3a801..b934af59550 100644
--- a/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.ts
+++ b/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.ts
@@ -5,10 +5,10 @@ for (let i = 0; i < 10; i++) {
}
}
// ==SCOPE::Extract to constant in global scope==
-const newLocal = 1;
-
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
+ const newLocal = 1;
+
let x = /*RENAME*/newLocal;
}
}
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition.js b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition1.js
similarity index 100%
rename from tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition.js
rename to tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition1.js
diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition.ts b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition1.ts
similarity index 100%
rename from tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition.ts
rename to tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition1.ts
diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.js b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.js
new file mode 100644
index 00000000000..137fc5ff0bb
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.js
@@ -0,0 +1,34 @@
+// ==ORIGINAL==
+class C {
+ a = 1;
+ M1() { }
+ b = 2;
+ M2() { }
+ M3() {
+ let x = 1;
+ }
+}
+// ==SCOPE::Extract to constant in method 'M3==
+class C {
+ a = 1;
+ M1() { }
+ b = 2;
+ M2() { }
+ M3() {
+ const newLocal = 1;
+
+ let x = /*RENAME*/newLocal;
+ }
+}
+// ==SCOPE::Extract to constant in global scope==
+const newLocal = 1;
+
+class C {
+ a = 1;
+ M1() { }
+ b = 2;
+ M2() { }
+ M3() {
+ let x = /*RENAME*/newLocal;
+ }
+}
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.ts b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.ts
new file mode 100644
index 00000000000..5bd239c0e63
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition2.ts
@@ -0,0 +1,46 @@
+// ==ORIGINAL==
+class C {
+ a = 1;
+ M1() { }
+ b = 2;
+ M2() { }
+ M3() {
+ let x = 1;
+ }
+}
+// ==SCOPE::Extract to constant in method 'M3==
+class C {
+ a = 1;
+ M1() { }
+ b = 2;
+ M2() { }
+ M3() {
+ const newLocal = 1;
+
+ let x = /*RENAME*/newLocal;
+ }
+}
+// ==SCOPE::Extract to readonly field in class 'C'==
+class C {
+ a = 1;
+ private readonly newProperty = 1;
+
+ M1() { }
+ b = 2;
+ M2() { }
+ M3() {
+ let x = this./*RENAME*/newProperty;
+ }
+}
+// ==SCOPE::Extract to constant in global scope==
+const newLocal = 1;
+
+class C {
+ a = 1;
+ M1() { }
+ b = 2;
+ M2() { }
+ M3() {
+ let x = /*RENAME*/newLocal;
+ }
+}
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.js b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.js
new file mode 100644
index 00000000000..1679e46d8d5
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.js
@@ -0,0 +1,34 @@
+// ==ORIGINAL==
+class C {
+ M1() { }
+ a = 1;
+ b = 2;
+ M2() { }
+ M3() {
+ let x = 1;
+ }
+}
+// ==SCOPE::Extract to constant in method 'M3==
+class C {
+ M1() { }
+ a = 1;
+ b = 2;
+ M2() { }
+ M3() {
+ const newLocal = 1;
+
+ let x = /*RENAME*/newLocal;
+ }
+}
+// ==SCOPE::Extract to constant in global scope==
+const newLocal = 1;
+
+class C {
+ M1() { }
+ a = 1;
+ b = 2;
+ M2() { }
+ M3() {
+ let x = /*RENAME*/newLocal;
+ }
+}
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.ts b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.ts
new file mode 100644
index 00000000000..4cc44565fa0
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition3.ts
@@ -0,0 +1,46 @@
+// ==ORIGINAL==
+class C {
+ M1() { }
+ a = 1;
+ b = 2;
+ M2() { }
+ M3() {
+ let x = 1;
+ }
+}
+// ==SCOPE::Extract to constant in method 'M3==
+class C {
+ M1() { }
+ a = 1;
+ b = 2;
+ M2() { }
+ M3() {
+ const newLocal = 1;
+
+ let x = /*RENAME*/newLocal;
+ }
+}
+// ==SCOPE::Extract to readonly field in class 'C'==
+class C {
+ M1() { }
+ a = 1;
+ b = 2;
+ M2() { }
+ private readonly newProperty = 1;
+
+ M3() {
+ let x = this./*RENAME*/newProperty;
+ }
+}
+// ==SCOPE::Extract to constant in global scope==
+const newLocal = 1;
+
+class C {
+ M1() { }
+ a = 1;
+ b = 2;
+ M2() { }
+ M3() {
+ let x = /*RENAME*/newLocal;
+ }
+}
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_Directive.js b/tests/baselines/reference/extractConstant/extractConstant_Directive.js
new file mode 100644
index 00000000000..5ba48e27de9
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_Directive.js
@@ -0,0 +1,14 @@
+// ==ORIGINAL==
+
+"strict";
+
+const x = 2 + 1;
+
+// ==SCOPE::Extract to constant in global scope==
+
+"strict";
+
+const newLocal = 2 + 1;
+
+const x = /*RENAME*/newLocal;
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_Directive.ts b/tests/baselines/reference/extractConstant/extractConstant_Directive.ts
new file mode 100644
index 00000000000..5ba48e27de9
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_Directive.ts
@@ -0,0 +1,14 @@
+// ==ORIGINAL==
+
+"strict";
+
+const x = 2 + 1;
+
+// ==SCOPE::Extract to constant in global scope==
+
+"strict";
+
+const newLocal = 2 + 1;
+
+const x = /*RENAME*/newLocal;
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.js b/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.js
new file mode 100644
index 00000000000..d2d0f14c01e
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.js
@@ -0,0 +1,22 @@
+// ==ORIGINAL==
+
+/*! Copyright */
+
+///
+
+"strict";
+
+const x = 2 + 1;
+
+// ==SCOPE::Extract to constant in global scope==
+
+/*! Copyright */
+
+///
+
+"strict";
+
+const newLocal = 2 + 1;
+
+const x = /*RENAME*/newLocal;
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.ts b/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.ts
new file mode 100644
index 00000000000..d2d0f14c01e
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_MultipleHeaders.ts
@@ -0,0 +1,22 @@
+// ==ORIGINAL==
+
+/*! Copyright */
+
+///
+
+"strict";
+
+const x = 2 + 1;
+
+// ==SCOPE::Extract to constant in global scope==
+
+/*! Copyright */
+
+///
+
+"strict";
+
+const newLocal = 2 + 1;
+
+const x = /*RENAME*/newLocal;
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.js b/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.js
new file mode 100644
index 00000000000..a372457fc48
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.js
@@ -0,0 +1,14 @@
+// ==ORIGINAL==
+
+/*! Copyright */
+
+const x = 2 + 1;
+
+// ==SCOPE::Extract to constant in global scope==
+
+/*! Copyright */
+
+const newLocal = 2 + 1;
+
+const x = /*RENAME*/newLocal;
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.ts b/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.ts
new file mode 100644
index 00000000000..a372457fc48
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_PinnedComment.ts
@@ -0,0 +1,14 @@
+// ==ORIGINAL==
+
+/*! Copyright */
+
+const x = 2 + 1;
+
+// ==SCOPE::Extract to constant in global scope==
+
+/*! Copyright */
+
+const newLocal = 2 + 1;
+
+const x = /*RENAME*/newLocal;
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.js b/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.js
new file mode 100644
index 00000000000..763473d49cd
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.js
@@ -0,0 +1,16 @@
+// ==ORIGINAL==
+
+/*! Copyright */
+
+/* About x */
+const x = 2 + 1;
+
+// ==SCOPE::Extract to constant in global scope==
+
+/*! Copyright */
+
+/* About x */
+const newLocal = 2 + 1;
+
+const x = /*RENAME*/newLocal;
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.ts b/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.ts
new file mode 100644
index 00000000000..763473d49cd
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_PinnedCommentAndDocComment.ts
@@ -0,0 +1,16 @@
+// ==ORIGINAL==
+
+/*! Copyright */
+
+/* About x */
+const x = 2 + 1;
+
+// ==SCOPE::Extract to constant in global scope==
+
+/*! Copyright */
+
+/* About x */
+const newLocal = 2 + 1;
+
+const x = /*RENAME*/newLocal;
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.js
new file mode 100644
index 00000000000..f881a021232
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.js
@@ -0,0 +1,16 @@
+// ==ORIGINAL==
+
+const i = 0;
+for (let j = 0; j < 10; j++) {
+ const x = i + 1;
+}
+
+// ==SCOPE::Extract to constant in global scope==
+
+const i = 0;
+for (let j = 0; j < 10; j++) {
+ const newLocal = i + 1;
+
+ const x = /*RENAME*/newLocal;
+}
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.ts
new file mode 100644
index 00000000000..f881a021232
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition1.ts
@@ -0,0 +1,16 @@
+// ==ORIGINAL==
+
+const i = 0;
+for (let j = 0; j < 10; j++) {
+ const x = i + 1;
+}
+
+// ==SCOPE::Extract to constant in global scope==
+
+const i = 0;
+for (let j = 0; j < 10; j++) {
+ const newLocal = i + 1;
+
+ const x = /*RENAME*/newLocal;
+}
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.js
new file mode 100644
index 00000000000..7ffd89161a2
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.js
@@ -0,0 +1,31 @@
+// ==ORIGINAL==
+
+const i = 0;
+function F() {
+ for (let j = 0; j < 10; j++) {
+ const x = i + 1;
+ }
+}
+
+// ==SCOPE::Extract to constant in function 'F'==
+
+const i = 0;
+function F() {
+ for (let j = 0; j < 10; j++) {
+ const newLocal = i + 1;
+
+ const x = /*RENAME*/newLocal;
+ }
+}
+
+// ==SCOPE::Extract to constant in global scope==
+
+const i = 0;
+const newLocal = i + 1;
+
+function F() {
+ for (let j = 0; j < 10; j++) {
+ const x = /*RENAME*/newLocal;
+ }
+}
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.ts
new file mode 100644
index 00000000000..7ffd89161a2
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition2.ts
@@ -0,0 +1,31 @@
+// ==ORIGINAL==
+
+const i = 0;
+function F() {
+ for (let j = 0; j < 10; j++) {
+ const x = i + 1;
+ }
+}
+
+// ==SCOPE::Extract to constant in function 'F'==
+
+const i = 0;
+function F() {
+ for (let j = 0; j < 10; j++) {
+ const newLocal = i + 1;
+
+ const x = /*RENAME*/newLocal;
+ }
+}
+
+// ==SCOPE::Extract to constant in global scope==
+
+const i = 0;
+const newLocal = i + 1;
+
+function F() {
+ for (let j = 0; j < 10; j++) {
+ const x = /*RENAME*/newLocal;
+ }
+}
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.js
new file mode 100644
index 00000000000..6ba0b2dfaae
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.js
@@ -0,0 +1,14 @@
+// ==ORIGINAL==
+
+for (let j = 0; j < 10; j++) {
+ const x = 2 + 1;
+}
+
+// ==SCOPE::Extract to constant in global scope==
+
+for (let j = 0; j < 10; j++) {
+ const newLocal = 2 + 1;
+
+ const x = /*RENAME*/newLocal;
+}
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.ts
new file mode 100644
index 00000000000..6ba0b2dfaae
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition3.ts
@@ -0,0 +1,14 @@
+// ==ORIGINAL==
+
+for (let j = 0; j < 10; j++) {
+ const x = 2 + 1;
+}
+
+// ==SCOPE::Extract to constant in global scope==
+
+for (let j = 0; j < 10; j++) {
+ const newLocal = 2 + 1;
+
+ const x = /*RENAME*/newLocal;
+}
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.js
new file mode 100644
index 00000000000..19fd25382e8
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.js
@@ -0,0 +1,28 @@
+// ==ORIGINAL==
+
+function F() {
+ for (let j = 0; j < 10; j++) {
+ const x = 2 + 1;
+ }
+}
+
+// ==SCOPE::Extract to constant in function 'F'==
+
+function F() {
+ for (let j = 0; j < 10; j++) {
+ const newLocal = 2 + 1;
+
+ const x = /*RENAME*/newLocal;
+ }
+}
+
+// ==SCOPE::Extract to constant in global scope==
+
+const newLocal = 2 + 1;
+
+function F() {
+ for (let j = 0; j < 10; j++) {
+ const x = /*RENAME*/newLocal;
+ }
+}
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.ts
new file mode 100644
index 00000000000..19fd25382e8
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition4.ts
@@ -0,0 +1,28 @@
+// ==ORIGINAL==
+
+function F() {
+ for (let j = 0; j < 10; j++) {
+ const x = 2 + 1;
+ }
+}
+
+// ==SCOPE::Extract to constant in function 'F'==
+
+function F() {
+ for (let j = 0; j < 10; j++) {
+ const newLocal = 2 + 1;
+
+ const x = /*RENAME*/newLocal;
+ }
+}
+
+// ==SCOPE::Extract to constant in global scope==
+
+const newLocal = 2 + 1;
+
+function F() {
+ for (let j = 0; j < 10; j++) {
+ const x = /*RENAME*/newLocal;
+ }
+}
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.js
new file mode 100644
index 00000000000..a19bd6afa28
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.js
@@ -0,0 +1,42 @@
+// ==ORIGINAL==
+
+function F0() {
+ function F1() {
+ function F2(x = 2 + 1) {
+ }
+ }
+}
+
+// ==SCOPE::Extract to constant in function 'F1'==
+
+function F0() {
+ function F1() {
+ const newLocal = 2 + 1;
+
+ function F2(x = /*RENAME*/newLocal) {
+ }
+ }
+}
+
+// ==SCOPE::Extract to constant in function 'F0'==
+
+function F0() {
+ const newLocal = 2 + 1;
+
+ function F1() {
+ function F2(x = /*RENAME*/newLocal) {
+ }
+ }
+}
+
+// ==SCOPE::Extract to constant in global scope==
+
+const newLocal = 2 + 1;
+
+function F0() {
+ function F1() {
+ function F2(x = /*RENAME*/newLocal) {
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.ts
new file mode 100644
index 00000000000..a19bd6afa28
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition5.ts
@@ -0,0 +1,42 @@
+// ==ORIGINAL==
+
+function F0() {
+ function F1() {
+ function F2(x = 2 + 1) {
+ }
+ }
+}
+
+// ==SCOPE::Extract to constant in function 'F1'==
+
+function F0() {
+ function F1() {
+ const newLocal = 2 + 1;
+
+ function F2(x = /*RENAME*/newLocal) {
+ }
+ }
+}
+
+// ==SCOPE::Extract to constant in function 'F0'==
+
+function F0() {
+ const newLocal = 2 + 1;
+
+ function F1() {
+ function F2(x = /*RENAME*/newLocal) {
+ }
+ }
+}
+
+// ==SCOPE::Extract to constant in global scope==
+
+const newLocal = 2 + 1;
+
+function F0() {
+ function F1() {
+ function F2(x = /*RENAME*/newLocal) {
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.js
new file mode 100644
index 00000000000..06a665140d9
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.js
@@ -0,0 +1,14 @@
+// ==ORIGINAL==
+
+class C {
+ x = 2 + 1;
+}
+
+// ==SCOPE::Extract to constant in global scope==
+
+const newLocal = 2 + 1;
+
+class C {
+ x = /*RENAME*/newLocal;
+}
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.ts
new file mode 100644
index 00000000000..a34b0a9ecc3
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition6.ts
@@ -0,0 +1,22 @@
+// ==ORIGINAL==
+
+class C {
+ x = 2 + 1;
+}
+
+// ==SCOPE::Extract to readonly field in class 'C'==
+
+class C {
+ private readonly newProperty = 2 + 1;
+
+ x = this./*RENAME*/newProperty;
+}
+
+// ==SCOPE::Extract to constant in global scope==
+
+const newLocal = 2 + 1;
+
+class C {
+ x = /*RENAME*/newLocal;
+}
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.js b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.js
new file mode 100644
index 00000000000..f73cba1b34c
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.js
@@ -0,0 +1,37 @@
+// ==ORIGINAL==
+
+const i = 0;
+class C {
+ M() {
+ for (let j = 0; j < 10; j++) {
+ x = i + 1;
+ }
+ }
+}
+
+// ==SCOPE::Extract to constant in method 'M==
+
+const i = 0;
+class C {
+ M() {
+ for (let j = 0; j < 10; j++) {
+ const newLocal = i + 1;
+
+ x = /*RENAME*/newLocal;
+ }
+ }
+}
+
+// ==SCOPE::Extract to constant in global scope==
+
+const i = 0;
+const newLocal = i + 1;
+
+class C {
+ M() {
+ for (let j = 0; j < 10; j++) {
+ x = /*RENAME*/newLocal;
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.ts b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.ts
new file mode 100644
index 00000000000..308df5785fb
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_StatementInsertionPosition7.ts
@@ -0,0 +1,50 @@
+// ==ORIGINAL==
+
+const i = 0;
+class C {
+ M() {
+ for (let j = 0; j < 10; j++) {
+ x = i + 1;
+ }
+ }
+}
+
+// ==SCOPE::Extract to constant in method 'M==
+
+const i = 0;
+class C {
+ M() {
+ for (let j = 0; j < 10; j++) {
+ const newLocal: any = i + 1;
+
+ x = /*RENAME*/newLocal;
+ }
+ }
+}
+
+// ==SCOPE::Extract to readonly field in class 'C'==
+
+const i = 0;
+class C {
+ private readonly newProperty: any = i + 1;
+
+ M() {
+ for (let j = 0; j < 10; j++) {
+ x = this./*RENAME*/newProperty;
+ }
+ }
+}
+
+// ==SCOPE::Extract to constant in global scope==
+
+const i = 0;
+const newLocal: any = i + 1;
+
+class C {
+ M() {
+ for (let j = 0; j < 10; j++) {
+ x = /*RENAME*/newLocal;
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.js b/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.js
new file mode 100644
index 00000000000..5003e8aa33e
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.js
@@ -0,0 +1,14 @@
+// ==ORIGINAL==
+
+///
+
+const x = 2 + 1;
+
+// ==SCOPE::Extract to constant in global scope==
+
+///
+
+const newLocal = 2 + 1;
+
+const x = /*RENAME*/newLocal;
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.ts b/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.ts
new file mode 100644
index 00000000000..5003e8aa33e
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_TripleSlash.ts
@@ -0,0 +1,14 @@
+// ==ORIGINAL==
+
+///
+
+const x = 2 + 1;
+
+// ==SCOPE::Extract to constant in global scope==
+
+///
+
+const newLocal = 2 + 1;
+
+const x = /*RENAME*/newLocal;
+
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.js b/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.js
new file mode 100644
index 00000000000..7582195a458
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.js
@@ -0,0 +1,6 @@
+// ==ORIGINAL==
+const /*About A*/a = 1,
+ /*About B*/b = a + 1;
+// ==SCOPE::Extract to constant in global scope==
+const /*About A*/a = 1,
+ /*About B*/newLocal = a + 1, b = /*RENAME*/newLocal;
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.ts b/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.ts
new file mode 100644
index 00000000000..7582195a458
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_VariableList_MultipleLines.ts
@@ -0,0 +1,6 @@
+// ==ORIGINAL==
+const /*About A*/a = 1,
+ /*About B*/b = a + 1;
+// ==SCOPE::Extract to constant in global scope==
+const /*About A*/a = 1,
+ /*About B*/newLocal = a + 1, b = /*RENAME*/newLocal;
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.js b/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.js
new file mode 100644
index 00000000000..efcc7a5a00c
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.js
@@ -0,0 +1,4 @@
+// ==ORIGINAL==
+const a = 1, b = a + 1;
+// ==SCOPE::Extract to constant in global scope==
+const a = 1, newLocal = a + 1, b = /*RENAME*/newLocal;
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.ts b/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.ts
new file mode 100644
index 00000000000..efcc7a5a00c
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_VariableList_const.ts
@@ -0,0 +1,4 @@
+// ==ORIGINAL==
+const a = 1, b = a + 1;
+// ==SCOPE::Extract to constant in global scope==
+const a = 1, newLocal = a + 1, b = /*RENAME*/newLocal;
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.js b/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.js
new file mode 100644
index 00000000000..dc0796596cb
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.js
@@ -0,0 +1,4 @@
+// ==ORIGINAL==
+let a = 1, b = a + 1;
+// ==SCOPE::Extract to constant in global scope==
+let a = 1, newLocal = a + 1, b = /*RENAME*/newLocal;
\ No newline at end of file
diff --git a/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.ts b/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.ts
new file mode 100644
index 00000000000..dc0796596cb
--- /dev/null
+++ b/tests/baselines/reference/extractConstant/extractConstant_VariableList_let.ts
@@ -0,0 +1,4 @@
+// ==ORIGINAL==
+let a = 1, b = a + 1;
+// ==SCOPE::Extract to constant in global scope==
+let a = 1, newLocal = a + 1, b = /*RENAME*/newLocal;
\ No newline at end of file