Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation of Extract Constant #18783

Merged
merged 9 commits into from
Sep 28, 2017

Conversation

amcasey
Copy link
Member

@amcasey amcasey commented Sep 27, 2017

[First, I propose standardizing on the terminology "extract function" and "extract constant", which, I argue, are more accurate than "extract method" and "extract local". This has no impact on the user-facing strings.]

This change generalizes the Extract Function refactoring (formerly, "Extract Method") to Extract Symbol and has it return both function and constant extractions. This required adding consideration for additional scopes into which it's sensible to insert constants but not functions. For Extract Function, the new scopes are immediately marked with errors.

This change also switches the tests to use the "public" API so that the two families of extractions can be distinguished. The baselines have been updated accordingly.

Outstanding work:

  1. Rename the extractMethod baselines for consistency. I didn't want the diff to be even worse.
  2. Improve the insertion locations of locals in function scopes. Right now, they are only inserted at the root level, which is frequently invalidated by the lack of access to variables from nested scopes.
  3. We build a bad syntax tree when substitutions are applied more than once.

Major changes:

1) Instead of skipping undesirable scopes, include them and mark them
with errors.  Constants can be extracted into more scopes.

2) Update the tests to call through the "public" API.  This caused some
baseline changes.

3) Rename refactoring to "Extract Symbol" for generality.

4) Return a second ApplicableRefactorInfo for constants.  Distinguish
the two by splitting the action name.
@@ -1,823 +0,0 @@
/// <reference path="..\harness.ts" />
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't detected as a rename because large chunks of it were pulled out into extractRanges.ts and extractSymbolTestHelpers.ts.

/// <reference path="extractTestHelpers.ts" />

namespace ts {
describe("extractConstants", () => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extractConstants [](start = 14, length = 16)

This test coverage isn't great because I didn't want to revise everything as soon as I updated the insertion locations.

}
`,
[
"Cannot extract range containing conditional break or continue statements."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we symbolically refer to these strings?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly. This was all copied from the old extractMethods.ts.


In reply to: 141221847 [](ancestors = 141221847)

const extractMethod: Refactor = {
name: "Extract Method",
description: Diagnostics.Extract_function.message,
namespace ts.refactor.extractSymbol {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extractSymbol [](start = 22, length = 13)

@RyanCavanaugh, the interesting changes are here.

namespace A {
export interface I { x: number };
class C {
a() {
let z = 1;
return this./*RENAME*/newFunction();
return this./*RENAME*/newMethod();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newMethod [](start = 34, length = 9)

Slightly non-trivial baseline update

refactorName: "Extract Method",
actionName: "scope_0",
refactorName: "Extract Symbol",
actionName: "function_scope_1",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 [](start = 32, length = 1)

0 is now the method itself. We won't insert a function there, but we will insert a local.

const visibleDeclarationsInExtractedRange: Symbol[] = [];

const expressionDiagnostics =
isReadonlyArray(targetRange.range) && !(targetRange.range.length === 1 && isExpressionStatement(targetRange.range[0]))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use "extract local" on this expression

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On which expression? targetRange.range?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything to the left of the ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That breaks control flow type checking.

isFunctionLikeDeclaration(scope) && scope.kind !== SyntaxKind.FunctionDeclaration
? [createDiagnosticForNode(scope, Messages.CannotExtractToOtherFunctionLike)]
: []);
constantErrorsPerScope.push(expressionDiagnostics);
Copy link
Member

@RyanCavanaugh RyanCavanaugh Sep 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The re-use of the same array object going into multiple positions in constantErrorsPerScope makes me nervous since this is currently mutable - let's change both to ReadonlyArray<Diagnostic>[]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading further down the function, I'm now confused

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.slice()

break;
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would assert prevChild !== undefined here since it's not obvious by construction that this is impossible

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but it follows from the fact that extraction position is in a child that begins before the extraction position (i.e. there will be at least one iteration and that iteration won't kick out based on the position).

// Confirm that the constraint is preserved.
testExtractMethod("extractMethod15",
`function F<T>(t1: T) {
function G<U extends T[]>(t2: U) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was it a test correctness thing to change F to G here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We de-dup actions by description and I wanted to see all of them.

@RyanCavanaugh
Copy link
Member

Testcase we were looking at

/*!
* MIT APACHE
*/
"use strict";
"use module";


namespace Y { }

namespace X {
    namespace Y {
        export const j = 10;
        namespace Z {
            export const y = j * j;
        }
    }
}

as well as

/// <reference ...

and

x = y; // Extract the whole thing

@amcasey
Copy link
Member Author

amcasey commented Sep 27, 2017

I can repro the assert in a unit test. This looks relevant: https://github.com/Microsoft/TypeScript/pull/18484/files#diff-20a9fa4d3e9d09f60c275a3dda245867

@amcasey
Copy link
Member Author

amcasey commented Sep 28, 2017

@RyanCavanaugh The problem we were seeing (in our case, an assert about token spans) will occur any time a substitution is applied more than once. Since the same issue affects Extract Function, I'm going to handle it in a separate PR.

@amcasey
Copy link
Member Author

amcasey commented Sep 28, 2017

@RyanCavanaugh I believe all outstanding issues can be addressed in subsequent PRs.

@amcasey
Copy link
Member Author

amcasey commented Sep 28, 2017

Many fixes to follow. 😄

@amcasey amcasey merged commit 5f30106 into microsoft:master Sep 28, 2017
@amcasey amcasey deleted the ExtractConstant branch September 28, 2017 17:09
@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants