@tslite/services 0.0.0-stage → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +55 -0
- package/NOTICE +13 -0
- package/dist/_namespaces/edit.d.ts +8 -0
- package/dist/_namespaces/edit.js +11 -0
- package/dist/_namespaces/ts.Completions.StringCompletions.d.ts +1 -0
- package/dist/_namespaces/ts.Completions.StringCompletions.js +2 -0
- package/dist/_namespaces/ts.Completions.d.ts +2 -0
- package/dist/_namespaces/ts.Completions.js +3 -0
- package/dist/_namespaces/ts.FindAllReferences.d.ts +2 -0
- package/dist/_namespaces/ts.FindAllReferences.js +3 -0
- package/dist/_namespaces/ts.GoToDefinition.d.ts +1 -0
- package/dist/_namespaces/ts.GoToDefinition.js +2 -0
- package/dist/_namespaces/ts.NavigationBar.d.ts +1 -0
- package/dist/_namespaces/ts.NavigationBar.js +2 -0
- package/dist/_namespaces/ts.OrganizeImports.d.ts +1 -0
- package/dist/_namespaces/ts.OrganizeImports.js +2 -0
- package/dist/_namespaces/ts.OutliningElementsCollector.d.ts +1 -0
- package/dist/_namespaces/ts.OutliningElementsCollector.js +2 -0
- package/dist/_namespaces/ts.Rename.d.ts +1 -0
- package/dist/_namespaces/ts.Rename.js +2 -0
- package/dist/_namespaces/ts.SignatureHelp.d.ts +1 -0
- package/dist/_namespaces/ts.SignatureHelp.js +2 -0
- package/dist/_namespaces/ts.SmartSelectionRange.d.ts +1 -0
- package/dist/_namespaces/ts.SmartSelectionRange.js +2 -0
- package/dist/_namespaces/ts.SymbolDisplay.d.ts +1 -0
- package/dist/_namespaces/ts.SymbolDisplay.js +2 -0
- package/dist/_namespaces/ts.codefix.d.ts +26 -0
- package/dist/_namespaces/ts.codefix.js +27 -0
- package/dist/_namespaces/ts.d.ts +17 -0
- package/dist/_namespaces/ts.formatting.d.ts +7 -0
- package/dist/_namespaces/ts.formatting.js +8 -0
- package/dist/_namespaces/ts.js +20 -0
- package/dist/_namespaces/ts.refactor.addOrRemoveBracesToArrowFunction.d.ts +1 -0
- package/dist/_namespaces/ts.refactor.addOrRemoveBracesToArrowFunction.js +2 -0
- package/dist/_namespaces/ts.refactor.convertParamsToDestructuredObject.d.ts +1 -0
- package/dist/_namespaces/ts.refactor.convertParamsToDestructuredObject.js +2 -0
- package/dist/_namespaces/ts.refactor.convertStringOrTemplateLiteral.d.ts +1 -0
- package/dist/_namespaces/ts.refactor.convertStringOrTemplateLiteral.js +2 -0
- package/dist/_namespaces/ts.refactor.d.ts +9 -0
- package/dist/_namespaces/ts.refactor.extractSymbol.d.ts +1 -0
- package/dist/_namespaces/ts.refactor.extractSymbol.js +2 -0
- package/dist/_namespaces/ts.refactor.js +10 -0
- package/dist/_namespaces/ts.textChanges.d.ts +1 -0
- package/dist/_namespaces/ts.textChanges.js +2 -0
- package/dist/classifier.d.ts +5 -0
- package/dist/classifier.js +439 -0
- package/dist/codeFixProvider.d.ts +13 -0
- package/dist/codeFixProvider.js +84 -0
- package/dist/codefixes/addConvertToUnknownForNonOverlappingTypes.d.ts +1 -0
- package/dist/codefixes/addConvertToUnknownForNonOverlappingTypes.js +21 -0
- package/dist/codefixes/addEmptyExportDeclaration.d.ts +1 -0
- package/dist/codefixes/addEmptyExportDeclaration.js +17 -0
- package/dist/codefixes/addMissingAsync.d.ts +1 -0
- package/dist/codefixes/addMissingAsync.js +75 -0
- package/dist/codefixes/addMissingAwait.d.ts +1 -0
- package/dist/codefixes/addMissingAwait.js +236 -0
- package/dist/codefixes/addMissingConst.d.ts +1 -0
- package/dist/codefixes/addMissingConst.js +91 -0
- package/dist/codefixes/addNameToNamelessParameter.d.ts +1 -0
- package/dist/codefixes/addNameToNamelessParameter.js +29 -0
- package/dist/codefixes/convertConstToLet.d.ts +1 -0
- package/dist/codefixes/convertConstToLet.js +30 -0
- package/dist/codefixes/convertToAsyncFunction.d.ts +1 -0
- package/dist/codefixes/convertToAsyncFunction.js +495 -0
- package/dist/codefixes/convertToMappedObjectType.d.ts +1 -0
- package/dist/codefixes/convertToMappedObjectType.js +48 -0
- package/dist/codefixes/correctQualifiedNameToIndexedAccessType.d.ts +1 -0
- package/dist/codefixes/correctQualifiedNameToIndexedAccessType.js +33 -0
- package/dist/codefixes/fixAddModuleReferTypeMissingTypeof.d.ts +1 -0
- package/dist/codefixes/fixAddModuleReferTypeMissingTypeof.js +27 -0
- package/dist/codefixes/fixAwaitInSyncFunction.d.ts +1 -0
- package/dist/codefixes/fixAwaitInSyncFunction.js +69 -0
- package/dist/codefixes/fixExpectedComma.d.ts +1 -0
- package/dist/codefixes/fixExpectedComma.js +35 -0
- package/dist/codefixes/fixInvalidJsxCharacters.d.ts +1 -0
- package/dist/codefixes/fixInvalidJsxCharacters.js +41 -0
- package/dist/codefixes/fixMissingCallParentheses.d.ts +1 -0
- package/dist/codefixes/fixMissingCallParentheses.js +42 -0
- package/dist/codefixes/fixSpelling.d.ts +1 -0
- package/dist/codefixes/fixSpelling.js +94 -0
- package/dist/codefixes/fixUnreachableCode.d.ts +1 -0
- package/dist/codefixes/fixUnreachableCode.js +65 -0
- package/dist/codefixes/fixUnusedIdentifier.d.ts +1 -0
- package/dist/codefixes/fixUnusedIdentifier.js +224 -0
- package/dist/codefixes/helpers.d.ts +8 -0
- package/dist/codefixes/helpers.js +53 -0
- package/dist/codefixes/importFixes.d.ts +14 -0
- package/dist/codefixes/importFixes.js +441 -0
- package/dist/codefixes/inferFromUsage.d.ts +1 -0
- package/dist/codefixes/inferFromUsage.js +742 -0
- package/dist/codefixes/removeUnnecessaryAwait.d.ts +1 -0
- package/dist/codefixes/removeUnnecessaryAwait.js +41 -0
- package/dist/codefixes/returnValueCorrect.d.ts +1 -0
- package/dist/codefixes/returnValueCorrect.js +132 -0
- package/dist/codefixes/splitTypeOnlyImport.d.ts +1 -0
- package/dist/codefixes/splitTypeOnlyImport.js +32 -0
- package/dist/codefixes/useBigintLiteral.d.ts +1 -0
- package/dist/codefixes/useBigintLiteral.js +29 -0
- package/dist/completions.d.ts +83 -0
- package/dist/completions.js +1720 -0
- package/dist/documentHighlights.d.ts +8 -0
- package/dist/documentHighlights.js +367 -0
- package/dist/documentRegistry.d.ts +36 -0
- package/dist/documentRegistry.js +63 -0
- package/dist/editingServices.d.ts +1 -0
- package/dist/editingServices.js +4 -0
- package/dist/findAllReferences.d.ts +113 -0
- package/dist/findAllReferences.js +1366 -0
- package/dist/formatting/formatting.d.ts +26 -0
- package/dist/formatting/formatting.js +1021 -0
- package/dist/formatting/formattingContext.d.ts +34 -0
- package/dist/formatting/formattingContext.js +78 -0
- package/dist/formatting/formattingScanner.d.ts +14 -0
- package/dist/formatting/formattingScanner.js +228 -0
- package/dist/formatting/rule.d.ts +31 -0
- package/dist/formatting/rule.js +22 -0
- package/dist/formatting/rules.d.ts +7 -0
- package/dist/formatting/rules.js +610 -0
- package/dist/formatting/rulesMap.d.ts +5 -0
- package/dist/formatting/rulesMap.js +127 -0
- package/dist/formatting/smartIndenter.d.ts +44 -0
- package/dist/formatting/smartIndenter.js +518 -0
- package/dist/getEditsForFileRename.d.ts +6 -0
- package/dist/getEditsForFileRename.js +86 -0
- package/dist/goToDefinition.d.ts +8 -0
- package/dist/goToDefinition.js +246 -0
- package/dist/importTracker.d.ts +50 -0
- package/dist/importTracker.js +422 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/navigationBar.d.ts +3 -0
- package/dist/navigationBar.js +549 -0
- package/dist/organizeImports.d.ts +18 -0
- package/dist/organizeImports.js +290 -0
- package/dist/outliningElementsCollector.d.ts +2 -0
- package/dist/outliningElementsCollector.js +261 -0
- package/dist/refactorProvider.d.ts +5 -0
- package/dist/refactorProvider.js +16 -0
- package/dist/refactors/addOrRemoveBracesToArrowFunction.d.ts +1 -0
- package/dist/refactors/addOrRemoveBracesToArrowFunction.js +86 -0
- package/dist/refactors/convertExport.d.ts +1 -0
- package/dist/refactors/convertExport.js +126 -0
- package/dist/refactors/convertImport.d.ts +1 -0
- package/dist/refactors/convertImport.js +116 -0
- package/dist/refactors/convertParamsToDestructuredObject.d.ts +1 -0
- package/dist/refactors/convertParamsToDestructuredObject.js +282 -0
- package/dist/refactors/convertStringOrTemplateLiteral.d.ts +1 -0
- package/dist/refactors/convertStringOrTemplateLiteral.js +148 -0
- package/dist/refactors/extractSymbol.d.ts +60 -0
- package/dist/refactors/extractSymbol.js +1281 -0
- package/dist/refactors/extractType.d.ts +1 -0
- package/dist/refactors/extractType.js +102 -0
- package/dist/refactors/moveToNewFile.d.ts +1 -0
- package/dist/refactors/moveToNewFile.js +477 -0
- package/dist/rename.d.ts +2 -0
- package/dist/rename.js +100 -0
- package/dist/services.d.ts +48 -0
- package/dist/services.js +1480 -0
- package/dist/signatureHelp.d.ts +8 -0
- package/dist/signatureHelp.js +414 -0
- package/dist/smartSelection.d.ts +2 -0
- package/dist/smartSelection.js +243 -0
- package/dist/stringCompletions.d.ts +4 -0
- package/dist/stringCompletions.js +220 -0
- package/dist/suggestionDiagnostics.d.ts +6 -0
- package/dist/suggestionDiagnostics.js +92 -0
- package/dist/symbolDisplay.d.ts +16 -0
- package/dist/symbolDisplay.js +400 -0
- package/dist/textChanges.d.ts +161 -0
- package/dist/textChanges.js +1163 -0
- package/dist/types.d.ts +908 -0
- package/dist/types.js +263 -0
- package/dist/utilities.d.ts +283 -0
- package/dist/utilities.js +1981 -0
- package/package.json +45 -3
- package/README.md +0 -3
|
@@ -0,0 +1,1281 @@
|
|
|
1
|
+
// Modificado de microsoft/TypeScript v3.9.10, src/services/refactors/extractSymbol.ts: o namespace virou módulo ES, com os imports explícitos.
|
|
2
|
+
// A extração para uma função escreve a forma da Linguagem: um `const` com uma arrow, e não a `function` declarada (CK-69).
|
|
3
|
+
// Como o `const` não é içado, ele entra antes do statement que o usa, onde a extração para uma constante o põe. Saem a
|
|
4
|
+
// classe como escopo, com o método, a propriedade `readonly` e o que é estático; o `this` e o `super`; o generator; o
|
|
5
|
+
// statement com label; o enum; e a function expression (CK-02). Saem também o arquivo JavaScript e o JSDoc (CK-03).
|
|
6
|
+
import { Debug, DiagnosticCategory, Diagnostics, EmitFlags, ModifierFlags, NodeBuilderFlags, NodeFlags, SymbolFlags, SyntaxKind, ScriptTarget, TypeFlags, arrayFrom, codefix, compareProperties, compareStringsCaseSensitive, compareValues, createArrowFunction, createAssignment, createAwait, createBindingElement, createBlock, createCall, createDiagnosticForNode, createFileDiagnostic, createIdentifier, createKeywordTypeNode, createMap, createNodeArray, createObjectBindingPattern, createObjectLiteral, createParameter, createPropertyAccess, createPropertyAssignment, createPropertySignature, createQualifiedName, createReturn, createShorthandPropertyAssignment, createStatement, createToken, createTypeLiteralNode, createTypeReferenceNode, createUnionTypeNode, createVariableDeclaration, createVariableDeclarationList, createVariableStatement, emptyArray, find, findAncestor, findTokenOnLeftOfPosition, first, forEachChild, formatStringFromArgs, getEffectiveTypeParameterDeclarations, getEnclosingBlockScopeContainer, getLocaleSpecificMessage, getNodeId, getParentNodeInSpan, getRefactorContextSpan, getRenameLocation, getSymbolId, getSynthesizedDeepClone, getTokenAtPosition, getUniqueName, hasModifier, isArray, isArrowFunction, isAssignmentExpression, isBlock, isBlockScope, isCaseClause, isDeclaration, isDeclarationWithTypeParameters, isElementAccessExpression, isExpression, isExpressionNode, isExpressionStatement, isFunctionLikeDeclaration, isIdentifier, isIterationStatement, isModuleBlock, isParenthesizedTypeNode, isPartOfTypeNode, isPropertyAccessExpression, isQualifiedName, isReturnStatement, isShorthandPropertyAssignment, isSourceFile, isStatement, isSwitchStatement, isUnaryExpressionWithWrite, isUnionTypeNode, isVariableDeclaration, isVariableDeclarationList, isVariableStatement, last, map, nullTransformationContext, positionIsSynthesized, rangeContainsStartEnd, setEmitFlags, singleOrUndefined, suppressLeadingAndTrailingTrivia, textChanges, textSpanEnd, updateArrowFunction, updateParameter, visitEachChild, visitNode, visitNodes, } from "../_namespaces/edit.js";
|
|
7
|
+
import { registerRefactor, } from "../_namespaces/ts.refactor.js";
|
|
8
|
+
const refactorName = "Extract Symbol";
|
|
9
|
+
registerRefactor(refactorName, { getAvailableActions, getEditsForAction });
|
|
10
|
+
/**
|
|
11
|
+
* Compute the associated code actions
|
|
12
|
+
* Exported for tests.
|
|
13
|
+
*/
|
|
14
|
+
export function getAvailableActions(context) {
|
|
15
|
+
const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context));
|
|
16
|
+
const targetRange = rangeToExtract.targetRange;
|
|
17
|
+
if (targetRange === undefined) {
|
|
18
|
+
return emptyArray;
|
|
19
|
+
}
|
|
20
|
+
const extractions = getPossibleExtractions(targetRange, context);
|
|
21
|
+
if (extractions === undefined) {
|
|
22
|
+
// No extractions possible
|
|
23
|
+
return emptyArray;
|
|
24
|
+
}
|
|
25
|
+
const functionActions = [];
|
|
26
|
+
const usedFunctionNames = createMap();
|
|
27
|
+
const constantActions = [];
|
|
28
|
+
const usedConstantNames = createMap();
|
|
29
|
+
let i = 0;
|
|
30
|
+
for (const { functionExtraction, constantExtraction } of extractions) {
|
|
31
|
+
// Skip these since we don't have a way to report errors yet
|
|
32
|
+
if (functionExtraction.errors.length === 0) {
|
|
33
|
+
// Don't issue refactorings with duplicated names.
|
|
34
|
+
// Scopes come back in "innermost first" order, so extractions will
|
|
35
|
+
// preferentially go into nearer scopes
|
|
36
|
+
const description = functionExtraction.description;
|
|
37
|
+
if (!usedFunctionNames.has(description)) {
|
|
38
|
+
usedFunctionNames.set(description, true);
|
|
39
|
+
functionActions.push({
|
|
40
|
+
description,
|
|
41
|
+
name: `function_scope_${i}`
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Skip these since we don't have a way to report errors yet
|
|
46
|
+
if (constantExtraction.errors.length === 0) {
|
|
47
|
+
// Don't issue refactorings with duplicated names.
|
|
48
|
+
// Scopes come back in "innermost first" order, so extractions will
|
|
49
|
+
// preferentially go into nearer scopes
|
|
50
|
+
const description = constantExtraction.description;
|
|
51
|
+
if (!usedConstantNames.has(description)) {
|
|
52
|
+
usedConstantNames.set(description, true);
|
|
53
|
+
constantActions.push({
|
|
54
|
+
description,
|
|
55
|
+
name: `constant_scope_${i}`
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// *do* increment i anyway because we'll look for the i-th scope
|
|
60
|
+
// later when actually doing the refactoring if the user requests it
|
|
61
|
+
i++;
|
|
62
|
+
}
|
|
63
|
+
const infos = [];
|
|
64
|
+
if (constantActions.length) {
|
|
65
|
+
infos.push({
|
|
66
|
+
name: refactorName,
|
|
67
|
+
description: getLocaleSpecificMessage(Diagnostics.Extract_constant),
|
|
68
|
+
actions: constantActions
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (functionActions.length) {
|
|
72
|
+
infos.push({
|
|
73
|
+
name: refactorName,
|
|
74
|
+
description: getLocaleSpecificMessage(Diagnostics.Extract_function),
|
|
75
|
+
actions: functionActions
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return infos.length ? infos : emptyArray;
|
|
79
|
+
}
|
|
80
|
+
/* Exported for tests */
|
|
81
|
+
export function getEditsForAction(context, actionName) {
|
|
82
|
+
const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context));
|
|
83
|
+
const targetRange = rangeToExtract.targetRange; // TODO:GH#18217
|
|
84
|
+
const parsedFunctionIndexMatch = /^function_scope_(\d+)$/.exec(actionName);
|
|
85
|
+
if (parsedFunctionIndexMatch) {
|
|
86
|
+
const index = +parsedFunctionIndexMatch[1];
|
|
87
|
+
Debug.assert(isFinite(index), "Expected to parse a finite number from the function scope index");
|
|
88
|
+
return getFunctionExtractionAtIndex(targetRange, context, index);
|
|
89
|
+
}
|
|
90
|
+
const parsedConstantIndexMatch = /^constant_scope_(\d+)$/.exec(actionName);
|
|
91
|
+
if (parsedConstantIndexMatch) {
|
|
92
|
+
const index = +parsedConstantIndexMatch[1];
|
|
93
|
+
Debug.assert(isFinite(index), "Expected to parse a finite number from the constant scope index");
|
|
94
|
+
return getConstantExtractionAtIndex(targetRange, context, index);
|
|
95
|
+
}
|
|
96
|
+
Debug.fail("Unrecognized action name");
|
|
97
|
+
}
|
|
98
|
+
// Move these into diagnostic messages if they become user-facing. Na tag, também as do `super`, do JSDoc, do label, do
|
|
99
|
+
// generator, da propriedade `readonly` de uma classe, da classe de um arquivo JavaScript e da `function` global que a
|
|
100
|
+
// extração deixaria fora do alcance, que saem com o que as dispara (CK-02, CK-03).
|
|
101
|
+
export var Messages;
|
|
102
|
+
(function (Messages) {
|
|
103
|
+
function createMessage(message) {
|
|
104
|
+
return { message, code: 0, category: DiagnosticCategory.Message, key: message };
|
|
105
|
+
}
|
|
106
|
+
Messages.cannotExtractRange = createMessage("Cannot extract range.");
|
|
107
|
+
Messages.cannotExtractImport = createMessage("Cannot extract import statement.");
|
|
108
|
+
Messages.cannotExtractEmpty = createMessage("Cannot extract empty range.");
|
|
109
|
+
Messages.expressionExpected = createMessage("expression expected.");
|
|
110
|
+
Messages.uselessConstantType = createMessage("No reason to extract constant of type.");
|
|
111
|
+
Messages.statementOrExpressionExpected = createMessage("Statement or expression expected.");
|
|
112
|
+
Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements = createMessage("Cannot extract range containing conditional break or continue statements.");
|
|
113
|
+
Messages.cannotExtractRangeContainingConditionalReturnStatement = createMessage("Cannot extract range containing conditional return statement.");
|
|
114
|
+
Messages.typeWillNotBeVisibleInTheNewScope = createMessage("Type will not visible in the new scope.");
|
|
115
|
+
Messages.cannotExtractIdentifier = createMessage("Select more than a single identifier.");
|
|
116
|
+
Messages.cannotExtractExportedEntity = createMessage("Cannot extract exported declaration");
|
|
117
|
+
Messages.cannotWriteInExpression = createMessage("Cannot write back side-effects when extracting an expression");
|
|
118
|
+
Messages.cannotExtractAmbientBlock = createMessage("Cannot extract code from ambient contexts");
|
|
119
|
+
Messages.cannotAccessVariablesFromNestedScopes = createMessage("Cannot access variables from nested scopes");
|
|
120
|
+
Messages.cannotExtractToOtherFunctionLike = createMessage("Cannot extract method to a function-like scope that is not a function");
|
|
121
|
+
Messages.cannotExtractToExpressionArrowFunction = createMessage("Cannot extract constant to an arrow function without a block");
|
|
122
|
+
})(Messages || (Messages = {}));
|
|
123
|
+
// Na tag, também o generator, o uso do `this` e a região estática de uma classe (CK-02).
|
|
124
|
+
var RangeFacts;
|
|
125
|
+
(function (RangeFacts) {
|
|
126
|
+
RangeFacts[RangeFacts["None"] = 0] = "None";
|
|
127
|
+
RangeFacts[RangeFacts["HasReturn"] = 1] = "HasReturn";
|
|
128
|
+
RangeFacts[RangeFacts["IsAsyncFunction"] = 4] = "IsAsyncFunction";
|
|
129
|
+
})(RangeFacts || (RangeFacts = {}));
|
|
130
|
+
/**
|
|
131
|
+
* getRangeToExtract takes a span inside a text file and returns either an expression or an array
|
|
132
|
+
* of statements representing the minimum set of nodes needed to extract the entire span. This
|
|
133
|
+
* process may fail, in which case a set of errors is returned instead (these are currently
|
|
134
|
+
* not shown to the user, but can be used by us diagnostically)
|
|
135
|
+
*/
|
|
136
|
+
// exported only for tests
|
|
137
|
+
export function getRangeToExtract(sourceFile, span) {
|
|
138
|
+
const { length } = span;
|
|
139
|
+
if (length === 0) {
|
|
140
|
+
return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractEmpty)] };
|
|
141
|
+
}
|
|
142
|
+
// Walk up starting from the the start position until we find a non-SourceFile node that subsumes the selected span.
|
|
143
|
+
// This may fail (e.g. you select two statements in the root of a source file)
|
|
144
|
+
const start = getParentNodeInSpan(getTokenAtPosition(sourceFile, span.start), sourceFile, span);
|
|
145
|
+
// Do the same for the ending position
|
|
146
|
+
const end = getParentNodeInSpan(findTokenOnLeftOfPosition(sourceFile, textSpanEnd(span)), sourceFile, span);
|
|
147
|
+
const declarations = [];
|
|
148
|
+
// We'll modify these flags as we walk the tree to collect data
|
|
149
|
+
// about what things need to be done as part of the extraction.
|
|
150
|
+
let rangeFacts = RangeFacts.None;
|
|
151
|
+
if (!start || !end) {
|
|
152
|
+
// cannot find either start or end node
|
|
153
|
+
return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] };
|
|
154
|
+
}
|
|
155
|
+
if (start.parent !== end.parent) {
|
|
156
|
+
// start and end nodes belong to different subtrees
|
|
157
|
+
return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] };
|
|
158
|
+
}
|
|
159
|
+
if (start !== end) {
|
|
160
|
+
// start and end should be statements and parent should be either block or a source file
|
|
161
|
+
if (!isBlockLike(start.parent)) {
|
|
162
|
+
return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] };
|
|
163
|
+
}
|
|
164
|
+
const statements = [];
|
|
165
|
+
const start2 = start; // TODO: GH#18217 Need to alias `start` to get this to compile. See https://github.com/Microsoft/TypeScript/issues/19955#issuecomment-344118248
|
|
166
|
+
for (const statement of start2.parent.statements) {
|
|
167
|
+
if (statement === start || statements.length) {
|
|
168
|
+
const errors = checkNode(statement);
|
|
169
|
+
if (errors) {
|
|
170
|
+
return { errors };
|
|
171
|
+
}
|
|
172
|
+
statements.push(statement);
|
|
173
|
+
}
|
|
174
|
+
if (statement === end) {
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (!statements.length) {
|
|
179
|
+
// https://github.com/Microsoft/TypeScript/issues/20559
|
|
180
|
+
// Ranges like [|case 1: break;|] will fail to populate `statements` because
|
|
181
|
+
// they will never find `start` in `start.parent.statements`.
|
|
182
|
+
// Consider: We could support ranges like [|case 1:|] by refining them to just
|
|
183
|
+
// the expression.
|
|
184
|
+
return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] };
|
|
185
|
+
}
|
|
186
|
+
return { targetRange: { range: statements, facts: rangeFacts, declarations } };
|
|
187
|
+
}
|
|
188
|
+
// Na tag, a seleção de um JSDoc dava o erro dele (CK-03).
|
|
189
|
+
if (isReturnStatement(start) && !start.expression) {
|
|
190
|
+
// Makes no sense to extract an expression-less return statement.
|
|
191
|
+
return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] };
|
|
192
|
+
}
|
|
193
|
+
// We have a single node (start)
|
|
194
|
+
const node = refineNode(start);
|
|
195
|
+
const errors = checkRootNode(node) || checkNode(node);
|
|
196
|
+
if (errors) {
|
|
197
|
+
return { errors };
|
|
198
|
+
}
|
|
199
|
+
return { targetRange: { range: getStatementOrExpressionRange(node), facts: rangeFacts, declarations } }; // TODO: GH#18217
|
|
200
|
+
/**
|
|
201
|
+
* Attempt to refine the extraction node (generally, by shrinking it) to produce better results.
|
|
202
|
+
* @param node The unrefined extraction node.
|
|
203
|
+
*/
|
|
204
|
+
function refineNode(node) {
|
|
205
|
+
if (isReturnStatement(node)) {
|
|
206
|
+
if (node.expression) {
|
|
207
|
+
return node.expression;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else if (isVariableStatement(node)) {
|
|
211
|
+
let numInitializers = 0;
|
|
212
|
+
let lastInitializer;
|
|
213
|
+
for (const declaration of node.declarationList.declarations) {
|
|
214
|
+
if (declaration.initializer) {
|
|
215
|
+
numInitializers++;
|
|
216
|
+
lastInitializer = declaration.initializer;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (numInitializers === 1) {
|
|
220
|
+
return lastInitializer;
|
|
221
|
+
}
|
|
222
|
+
// No special handling if there are multiple initializers.
|
|
223
|
+
}
|
|
224
|
+
else if (isVariableDeclaration(node)) {
|
|
225
|
+
if (node.initializer) {
|
|
226
|
+
return node.initializer;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return node;
|
|
230
|
+
}
|
|
231
|
+
function checkRootNode(node) {
|
|
232
|
+
if (isIdentifier(isExpressionStatement(node) ? node.expression : node)) {
|
|
233
|
+
return [createDiagnosticForNode(node, Messages.cannotExtractIdentifier)];
|
|
234
|
+
}
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
// Na tag, o `checkForStaticContext` marcava a região estática de uma classe (CK-02).
|
|
238
|
+
// Verifies whether we can actually extract this node or not.
|
|
239
|
+
function checkNode(nodeToCheck) {
|
|
240
|
+
let PermittedJumps;
|
|
241
|
+
(function (PermittedJumps) {
|
|
242
|
+
PermittedJumps[PermittedJumps["None"] = 0] = "None";
|
|
243
|
+
PermittedJumps[PermittedJumps["Break"] = 1] = "Break";
|
|
244
|
+
PermittedJumps[PermittedJumps["Continue"] = 2] = "Continue";
|
|
245
|
+
PermittedJumps[PermittedJumps["Return"] = 4] = "Return";
|
|
246
|
+
})(PermittedJumps || (PermittedJumps = {}));
|
|
247
|
+
// We believe it's true because the node is from the (unmodified) tree.
|
|
248
|
+
Debug.assert(nodeToCheck.pos <= nodeToCheck.end, "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809 (1)");
|
|
249
|
+
// For understanding how skipTrivia functioned:
|
|
250
|
+
Debug.assert(!positionIsSynthesized(nodeToCheck.pos), "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809 (2)");
|
|
251
|
+
if (!isStatement(nodeToCheck) && !(isExpressionNode(nodeToCheck) && isExtractableExpression(nodeToCheck))) {
|
|
252
|
+
return [createDiagnosticForNode(nodeToCheck, Messages.statementOrExpressionExpected)];
|
|
253
|
+
}
|
|
254
|
+
if (nodeToCheck.flags & NodeFlags.Ambient) {
|
|
255
|
+
return [createDiagnosticForNode(nodeToCheck, Messages.cannotExtractAmbientBlock)];
|
|
256
|
+
}
|
|
257
|
+
let errors;
|
|
258
|
+
let permittedJumps = 4 /* PermittedJumps.Return */;
|
|
259
|
+
visit(nodeToCheck);
|
|
260
|
+
return errors;
|
|
261
|
+
function visit(node) {
|
|
262
|
+
if (errors) {
|
|
263
|
+
// already found an error - can stop now
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
if (isDeclaration(node)) {
|
|
267
|
+
const declaringNode = (node.kind === SyntaxKind.VariableDeclaration) ? node.parent.parent : node;
|
|
268
|
+
if (hasModifier(declaringNode, ModifierFlags.Export)) {
|
|
269
|
+
// TODO: GH#18217 Silly to use `errors ||` since it's definitely not defined (see top of `visit`)
|
|
270
|
+
// Also, if we're only pushing one error, just use `let error: Diagnostic | undefined`!
|
|
271
|
+
// Also TODO: GH#19956
|
|
272
|
+
(errors || (errors = [])).push(createDiagnosticForNode(node, Messages.cannotExtractExportedEntity));
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
declarations.push(node.symbol);
|
|
276
|
+
}
|
|
277
|
+
// Some things can't be extracted in certain situations. Na tag, também o `super` (CK-02).
|
|
278
|
+
switch (node.kind) {
|
|
279
|
+
case SyntaxKind.ImportDeclaration:
|
|
280
|
+
(errors || (errors = [])).push(createDiagnosticForNode(node, Messages.cannotExtractImport));
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
// Na tag, também a classe, e a `function` e a classe globais, que a extração tiraria do alcance (CK-02).
|
|
284
|
+
if (isFunctionLikeDeclaration(node)) {
|
|
285
|
+
// do not dive into functions
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
const savedPermittedJumps = permittedJumps;
|
|
289
|
+
switch (node.kind) {
|
|
290
|
+
case SyntaxKind.IfStatement:
|
|
291
|
+
permittedJumps = 0 /* PermittedJumps.None */;
|
|
292
|
+
break;
|
|
293
|
+
case SyntaxKind.TryStatement:
|
|
294
|
+
// forbid all jumps inside try blocks
|
|
295
|
+
permittedJumps = 0 /* PermittedJumps.None */;
|
|
296
|
+
break;
|
|
297
|
+
case SyntaxKind.Block:
|
|
298
|
+
if (node.parent && node.parent.kind === SyntaxKind.TryStatement && node.parent.finallyBlock === node) {
|
|
299
|
+
// allow unconditional returns from finally blocks
|
|
300
|
+
permittedJumps = 4 /* PermittedJumps.Return */;
|
|
301
|
+
}
|
|
302
|
+
break;
|
|
303
|
+
case SyntaxKind.DefaultClause:
|
|
304
|
+
case SyntaxKind.CaseClause:
|
|
305
|
+
// allow unlabeled break inside case clauses
|
|
306
|
+
permittedJumps |= 1 /* PermittedJumps.Break */;
|
|
307
|
+
break;
|
|
308
|
+
default:
|
|
309
|
+
if (isIterationStatement(node)) {
|
|
310
|
+
// allow unlabeled break/continue inside loops
|
|
311
|
+
permittedJumps |= 1 /* PermittedJumps.Break */ | 2 /* PermittedJumps.Continue */;
|
|
312
|
+
}
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
// Na tag, também o `this` e o tipo `this`, o statement com label com o salto para ele, e o `yield` (CK-02).
|
|
316
|
+
switch (node.kind) {
|
|
317
|
+
case SyntaxKind.BreakStatement:
|
|
318
|
+
case SyntaxKind.ContinueStatement: {
|
|
319
|
+
if (!(permittedJumps & (node.kind === SyntaxKind.BreakStatement ? 1 /* PermittedJumps.Break */ : 2 /* PermittedJumps.Continue */))) {
|
|
320
|
+
// attempt to break or continue in a forbidden context
|
|
321
|
+
(errors || (errors = [])).push(createDiagnosticForNode(node, Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements));
|
|
322
|
+
}
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
case SyntaxKind.AwaitExpression:
|
|
326
|
+
rangeFacts |= RangeFacts.IsAsyncFunction;
|
|
327
|
+
break;
|
|
328
|
+
case SyntaxKind.ReturnStatement:
|
|
329
|
+
if (permittedJumps & 4 /* PermittedJumps.Return */) {
|
|
330
|
+
rangeFacts |= RangeFacts.HasReturn;
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
(errors || (errors = [])).push(createDiagnosticForNode(node, Messages.cannotExtractRangeContainingConditionalReturnStatement));
|
|
334
|
+
}
|
|
335
|
+
break;
|
|
336
|
+
default:
|
|
337
|
+
forEachChild(node, visit);
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
permittedJumps = savedPermittedJumps;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function getStatementOrExpressionRange(node) {
|
|
345
|
+
if (isStatement(node)) {
|
|
346
|
+
return [node];
|
|
347
|
+
}
|
|
348
|
+
else if (isExpressionNode(node)) {
|
|
349
|
+
// If our selection is the expression in an ExpressionStatement, expand
|
|
350
|
+
// the selection to include the enclosing Statement (this stops us
|
|
351
|
+
// from trying to care about the return value of the extracted function
|
|
352
|
+
// and eliminates double semicolon insertion in certain scenarios)
|
|
353
|
+
return isExpressionStatement(node.parent) ? [node.parent] : node;
|
|
354
|
+
}
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
function isScope(node) {
|
|
358
|
+
return isFunctionLikeDeclaration(node) || isSourceFile(node) || isModuleBlock(node);
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Computes possible places we could extract the function into. For example,
|
|
362
|
+
* you may be able to extract into a class method *or* local closure *or* namespace function,
|
|
363
|
+
* depending on what's in the extracted body.
|
|
364
|
+
*/
|
|
365
|
+
function collectEnclosingScopes(range) {
|
|
366
|
+
let current = isReadonlyArray(range.range) ? first(range.range) : range.range;
|
|
367
|
+
// Na tag, o trecho que usava o `this` numa classe só ia para um método dela (CK-02).
|
|
368
|
+
const scopes = [];
|
|
369
|
+
while (true) {
|
|
370
|
+
current = current.parent;
|
|
371
|
+
// A function parameter's initializer is actually in the outer scope, not the function declaration
|
|
372
|
+
if (current.kind === SyntaxKind.Parameter) {
|
|
373
|
+
// Skip all the way to the outer scope of the function that declared this parameter
|
|
374
|
+
current = findAncestor(current, parent => isFunctionLikeDeclaration(parent)).parent;
|
|
375
|
+
}
|
|
376
|
+
// We want to find the nearest parent where we can place an "equivalent" sibling to the node we're extracting out of.
|
|
377
|
+
// Walk up to the closest parent of a place where we can logically put a sibling:
|
|
378
|
+
// * Function declaration
|
|
379
|
+
// * Class declaration or expression
|
|
380
|
+
// * Module/namespace or source file
|
|
381
|
+
if (isScope(current)) {
|
|
382
|
+
scopes.push(current);
|
|
383
|
+
if (current.kind === SyntaxKind.SourceFile) {
|
|
384
|
+
return scopes;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function getFunctionExtractionAtIndex(targetRange, context, requestedChangesIndex) {
|
|
390
|
+
const { scopes, readsAndWrites: { target, usagesPerScope, functionErrorsPerScope, exposedVariableDeclarations } } = getPossibleExtractionsWorker(targetRange, context);
|
|
391
|
+
Debug.assert(!functionErrorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?");
|
|
392
|
+
context.cancellationToken.throwIfCancellationRequested(); // TODO: GH#18217
|
|
393
|
+
return extractFunctionInScope(target, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], exposedVariableDeclarations, targetRange, context);
|
|
394
|
+
}
|
|
395
|
+
function getConstantExtractionAtIndex(targetRange, context, requestedChangesIndex) {
|
|
396
|
+
const { scopes, readsAndWrites: { target, usagesPerScope, constantErrorsPerScope, exposedVariableDeclarations } } = getPossibleExtractionsWorker(targetRange, context);
|
|
397
|
+
Debug.assert(!constantErrorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?");
|
|
398
|
+
Debug.assert(exposedVariableDeclarations.length === 0, "Extract constant accepted a range containing a variable declaration?");
|
|
399
|
+
context.cancellationToken.throwIfCancellationRequested();
|
|
400
|
+
const expression = isExpression(target)
|
|
401
|
+
? target
|
|
402
|
+
: target.statements[0].expression;
|
|
403
|
+
return extractConstantInScope(expression, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], context);
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Given a piece of text to extract ('targetRange'), computes a list of possible extractions.
|
|
407
|
+
* Each returned ExtractResultForScope corresponds to a possible target scope and is either a set of changes
|
|
408
|
+
* or an error explaining why we can't extract into that scope.
|
|
409
|
+
*/
|
|
410
|
+
function getPossibleExtractions(targetRange, context) {
|
|
411
|
+
const { scopes, readsAndWrites: { functionErrorsPerScope, constantErrorsPerScope } } = getPossibleExtractionsWorker(targetRange, context);
|
|
412
|
+
// Need the inner type annotation to avoid https://github.com/Microsoft/TypeScript/issues/7547
|
|
413
|
+
const extractions = scopes.map((scope, i) => {
|
|
414
|
+
const functionDescriptionPart = getDescriptionForFunctionInScope(scope);
|
|
415
|
+
const constantDescriptionPart = getDescriptionForConstantInScope(scope);
|
|
416
|
+
const scopeDescription = isFunctionLikeDeclaration(scope)
|
|
417
|
+
? getDescriptionForFunctionLikeDeclaration(scope)
|
|
418
|
+
: getDescriptionForModuleLikeDeclaration(scope);
|
|
419
|
+
let functionDescription;
|
|
420
|
+
let constantDescription;
|
|
421
|
+
if (scopeDescription === 1 /* SpecialScope.Global */) {
|
|
422
|
+
functionDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [functionDescriptionPart, "global"]);
|
|
423
|
+
constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [constantDescriptionPart, "global"]);
|
|
424
|
+
}
|
|
425
|
+
else if (scopeDescription === 0 /* SpecialScope.Module */) {
|
|
426
|
+
functionDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [functionDescriptionPart, "module"]);
|
|
427
|
+
constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [constantDescriptionPart, "module"]);
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
functionDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1), [functionDescriptionPart, scopeDescription]);
|
|
431
|
+
constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1), [constantDescriptionPart, scopeDescription]);
|
|
432
|
+
}
|
|
433
|
+
// Customize the phrasing for the innermost scope to increase clarity.
|
|
434
|
+
if (i === 0) {
|
|
435
|
+
constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_enclosing_scope), [constantDescriptionPart]);
|
|
436
|
+
}
|
|
437
|
+
return {
|
|
438
|
+
functionExtraction: {
|
|
439
|
+
description: functionDescription,
|
|
440
|
+
errors: functionErrorsPerScope[i],
|
|
441
|
+
},
|
|
442
|
+
constantExtraction: {
|
|
443
|
+
description: constantDescription,
|
|
444
|
+
errors: constantErrorsPerScope[i],
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
});
|
|
448
|
+
return extractions;
|
|
449
|
+
}
|
|
450
|
+
function getPossibleExtractionsWorker(targetRange, context) {
|
|
451
|
+
const { file: sourceFile } = context;
|
|
452
|
+
const scopes = collectEnclosingScopes(targetRange);
|
|
453
|
+
const enclosingTextRange = getEnclosingTextRange(targetRange, sourceFile);
|
|
454
|
+
const readsAndWrites = collectReadsAndWrites(targetRange, scopes, enclosingTextRange, sourceFile, context.program.getTypeChecker(), context.cancellationToken);
|
|
455
|
+
return { scopes, readsAndWrites };
|
|
456
|
+
}
|
|
457
|
+
// Na tag, o método e o campo `readonly` de uma classe (CK-02).
|
|
458
|
+
function getDescriptionForFunctionInScope(scope) {
|
|
459
|
+
return isFunctionLikeDeclaration(scope)
|
|
460
|
+
? "inner function"
|
|
461
|
+
: "function";
|
|
462
|
+
}
|
|
463
|
+
function getDescriptionForConstantInScope(_scope) {
|
|
464
|
+
return "constant";
|
|
465
|
+
}
|
|
466
|
+
// Na tag, também o construtor, a `function` e a function expression, o método e os acessores, e a classe (CK-02).
|
|
467
|
+
function getDescriptionForFunctionLikeDeclaration(_scope) {
|
|
468
|
+
return "arrow function";
|
|
469
|
+
}
|
|
470
|
+
function getDescriptionForModuleLikeDeclaration(scope) {
|
|
471
|
+
return scope.kind === SyntaxKind.ModuleBlock
|
|
472
|
+
? `namespace '${scope.parent.name.getText()}'`
|
|
473
|
+
: scope.externalModuleIndicator ? 0 /* SpecialScope.Module */ : 1 /* SpecialScope.Global */;
|
|
474
|
+
}
|
|
475
|
+
var SpecialScope;
|
|
476
|
+
(function (SpecialScope) {
|
|
477
|
+
SpecialScope[SpecialScope["Module"] = 0] = "Module";
|
|
478
|
+
SpecialScope[SpecialScope["Global"] = 1] = "Global";
|
|
479
|
+
})(SpecialScope || (SpecialScope = {}));
|
|
480
|
+
/**
|
|
481
|
+
* Result of 'extractRange' operation for a specific scope.
|
|
482
|
+
* Stores either a list of changes that should be applied to extract a range or a list of errors
|
|
483
|
+
*/
|
|
484
|
+
function extractFunctionInScope(node, scope, { usages: usagesInScope, typeParameterUsages, substitutions }, exposedVariableDeclarations, range, context) {
|
|
485
|
+
const checker = context.program.getTypeChecker();
|
|
486
|
+
// O alvo da configuração fixa (CK-24).
|
|
487
|
+
const scriptTarget = ScriptTarget.ES2020;
|
|
488
|
+
const importAdder = codefix.createImportAdder(context.file, context.program, context.preferences, context.host);
|
|
489
|
+
// Make a unique name for the extracted function. Na tag, o nome era outro para um método (CK-02).
|
|
490
|
+
const file = scope.getSourceFile();
|
|
491
|
+
const functionNameText = getUniqueName("newFunction", file);
|
|
492
|
+
const functionName = createIdentifier(functionNameText);
|
|
493
|
+
let returnType;
|
|
494
|
+
const parameters = [];
|
|
495
|
+
const callArguments = [];
|
|
496
|
+
let writes;
|
|
497
|
+
// Na tag, o arquivo JavaScript não anotava os tipos (CK-03).
|
|
498
|
+
usagesInScope.forEach((usage, name) => {
|
|
499
|
+
let type = checker.getTypeOfSymbolAtLocation(usage.symbol, usage.node);
|
|
500
|
+
// Widen the type so we don't emit nonsense annotations like "function fn(x: 3) {"
|
|
501
|
+
type = checker.getBaseTypeOfLiteralType(type);
|
|
502
|
+
const typeNode = codefix.typeToAutoImportableTypeNode(checker, importAdder, type, scope, scriptTarget, NodeBuilderFlags.NoTruncation);
|
|
503
|
+
const paramDecl = createParameter(
|
|
504
|
+
/*modifiers*/ undefined,
|
|
505
|
+
/*dotDotDotToken*/ undefined,
|
|
506
|
+
/*name*/ name,
|
|
507
|
+
/*questionToken*/ undefined, typeNode);
|
|
508
|
+
parameters.push(paramDecl);
|
|
509
|
+
if (usage.usage === 2 /* Usage.Write */) {
|
|
510
|
+
(writes || (writes = [])).push(usage);
|
|
511
|
+
}
|
|
512
|
+
callArguments.push(createIdentifier(name));
|
|
513
|
+
});
|
|
514
|
+
const typeParametersAndDeclarations = arrayFrom(typeParameterUsages.values()).map(type => ({ type, declaration: getFirstDeclaration(type) }));
|
|
515
|
+
const sortedTypeParametersAndDeclarations = typeParametersAndDeclarations.sort(compareTypesByDeclarationOrder);
|
|
516
|
+
const typeParameters = sortedTypeParametersAndDeclarations.length === 0
|
|
517
|
+
? undefined
|
|
518
|
+
: sortedTypeParametersAndDeclarations.map(t => t.declaration);
|
|
519
|
+
// Strictly speaking, we should check whether each name actually binds to the appropriate type
|
|
520
|
+
// parameter. In cases of shadowing, they may not.
|
|
521
|
+
const callTypeArguments = typeParameters !== undefined
|
|
522
|
+
? typeParameters.map(decl => createTypeReferenceNode(decl.name, /*typeArguments*/ undefined))
|
|
523
|
+
: undefined;
|
|
524
|
+
// Provide explicit return types for contextually-typed functions
|
|
525
|
+
// to avoid problems when there are literal types present
|
|
526
|
+
if (isExpression(node)) {
|
|
527
|
+
const contextualType = checker.getContextualType(node);
|
|
528
|
+
returnType = checker.typeToTypeNode(contextualType, scope, NodeBuilderFlags.NoTruncation);
|
|
529
|
+
}
|
|
530
|
+
const { body, returnValueProperty } = transformFunctionBody(node, exposedVariableDeclarations, writes, substitutions, !!(range.facts & RangeFacts.HasReturn));
|
|
531
|
+
suppressLeadingAndTrailingTrivia(body);
|
|
532
|
+
// A função da Linguagem é um `const` com uma arrow (CK-69). Na tag, uma `function` declarada, ou, numa classe, um
|
|
533
|
+
// método privado, estático na região estática (CK-02); e a estrela do generator (CK-02).
|
|
534
|
+
const newFunction = createVariableStatement(
|
|
535
|
+
/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(functionName,
|
|
536
|
+
/*type*/ undefined, createArrowFunction(range.facts & RangeFacts.IsAsyncFunction ? [createToken(SyntaxKind.AsyncKeyword)] : undefined, typeParameters, parameters, returnType,
|
|
537
|
+
/*equalsGreaterThanToken*/ undefined, body))], NodeFlags.Const));
|
|
538
|
+
// O `const` não é içado como a `function`: ele entra antes do statement que o usa, onde a extração para uma constante
|
|
539
|
+
// o põe, e não depois do trecho, no fim do escopo, como a `function` da tag (CK-69).
|
|
540
|
+
const changeTracker = textChanges.ChangeTracker.fromContext(context);
|
|
541
|
+
const nodeToInsertBefore = getNodeToInsertConstantBefore(isReadonlyArray(range.range) ? first(range.range) : range.range, scope);
|
|
542
|
+
if (nodeToInsertBefore.pos === 0) {
|
|
543
|
+
changeTracker.insertNodeAtTopOfFile(context.file, newFunction, /*blankLineBetween*/ true);
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newFunction, /*blankLineBetween*/ true);
|
|
547
|
+
}
|
|
548
|
+
importAdder.writeFixes(changeTracker);
|
|
549
|
+
const newNodes = [];
|
|
550
|
+
// replace range with function call. Na tag, o método de uma classe se chamava pelo `this` ou pelo nome dela (CK-02).
|
|
551
|
+
const called = createIdentifier(functionNameText);
|
|
552
|
+
let call = createCall(called, callTypeArguments, // Note that no attempt is made to take advantage of type argument inference
|
|
553
|
+
callArguments);
|
|
554
|
+
if (range.facts & RangeFacts.IsAsyncFunction) {
|
|
555
|
+
call = createAwait(call);
|
|
556
|
+
}
|
|
557
|
+
if (exposedVariableDeclarations.length && !writes) {
|
|
558
|
+
// No need to mix declarations and writes.
|
|
559
|
+
// How could any variables be exposed if there's a return statement?
|
|
560
|
+
Debug.assert(!returnValueProperty, "Expected no returnValueProperty");
|
|
561
|
+
Debug.assert(!(range.facts & RangeFacts.HasReturn), "Expected RangeFacts.HasReturn flag to be unset");
|
|
562
|
+
if (exposedVariableDeclarations.length === 1) {
|
|
563
|
+
// Declaring exactly one variable: let x = newFunction();
|
|
564
|
+
const variableDeclaration = exposedVariableDeclarations[0];
|
|
565
|
+
newNodes.push(createVariableStatement(
|
|
566
|
+
/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(variableDeclaration.name), /*type*/ getSynthesizedDeepClone(variableDeclaration.type), /*initializer*/ call)], // TODO (acasey): test binding patterns
|
|
567
|
+
variableDeclaration.parent.flags)));
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
// Declaring multiple variables / return properties:
|
|
571
|
+
// let {x, y} = newFunction();
|
|
572
|
+
const bindingElements = [];
|
|
573
|
+
const typeElements = [];
|
|
574
|
+
let commonNodeFlags = exposedVariableDeclarations[0].parent.flags;
|
|
575
|
+
let sawExplicitType = false;
|
|
576
|
+
for (const variableDeclaration of exposedVariableDeclarations) {
|
|
577
|
+
bindingElements.push(createBindingElement(
|
|
578
|
+
/*dotDotDotToken*/ undefined,
|
|
579
|
+
/*propertyName*/ undefined,
|
|
580
|
+
/*name*/ getSynthesizedDeepClone(variableDeclaration.name)));
|
|
581
|
+
// Being returned through an object literal will have widened the type.
|
|
582
|
+
const variableType = checker.typeToTypeNode(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(variableDeclaration)), scope, NodeBuilderFlags.NoTruncation);
|
|
583
|
+
typeElements.push(createPropertySignature(
|
|
584
|
+
/*modifiers*/ undefined,
|
|
585
|
+
/*name*/ variableDeclaration.symbol.name,
|
|
586
|
+
/*questionToken*/ undefined,
|
|
587
|
+
/*type*/ variableType,
|
|
588
|
+
/*initializer*/ undefined));
|
|
589
|
+
sawExplicitType = sawExplicitType || variableDeclaration.type !== undefined;
|
|
590
|
+
commonNodeFlags = commonNodeFlags & variableDeclaration.parent.flags;
|
|
591
|
+
}
|
|
592
|
+
const typeLiteral = sawExplicitType ? createTypeLiteralNode(typeElements) : undefined;
|
|
593
|
+
if (typeLiteral) {
|
|
594
|
+
setEmitFlags(typeLiteral, EmitFlags.SingleLine);
|
|
595
|
+
}
|
|
596
|
+
newNodes.push(createVariableStatement(
|
|
597
|
+
/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(createObjectBindingPattern(bindingElements),
|
|
598
|
+
/*type*/ typeLiteral,
|
|
599
|
+
/*initializer*/ call)], commonNodeFlags)));
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
else if (exposedVariableDeclarations.length || writes) {
|
|
603
|
+
if (exposedVariableDeclarations.length) {
|
|
604
|
+
// CONSIDER: we're going to create one statement per variable, but we could actually preserve their original grouping.
|
|
605
|
+
for (const variableDeclaration of exposedVariableDeclarations) {
|
|
606
|
+
let flags = variableDeclaration.parent.flags;
|
|
607
|
+
if (flags & NodeFlags.Const) {
|
|
608
|
+
flags = (flags & ~NodeFlags.Const) | NodeFlags.Let;
|
|
609
|
+
}
|
|
610
|
+
newNodes.push(createVariableStatement(
|
|
611
|
+
/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(variableDeclaration.symbol.name, getTypeDeepCloneUnionUndefined(variableDeclaration.type))], flags)));
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
if (returnValueProperty) {
|
|
615
|
+
// has both writes and return, need to create variable declaration to hold return value;
|
|
616
|
+
newNodes.push(createVariableStatement(
|
|
617
|
+
/*modifiers*/ undefined, createVariableDeclarationList([createVariableDeclaration(returnValueProperty, getTypeDeepCloneUnionUndefined(returnType))], NodeFlags.Let)));
|
|
618
|
+
}
|
|
619
|
+
const assignments = getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes);
|
|
620
|
+
if (returnValueProperty) {
|
|
621
|
+
assignments.unshift(createShorthandPropertyAssignment(returnValueProperty));
|
|
622
|
+
}
|
|
623
|
+
// propagate writes back
|
|
624
|
+
if (assignments.length === 1) {
|
|
625
|
+
// We would only have introduced a return value property if there had been
|
|
626
|
+
// other assignments to make.
|
|
627
|
+
Debug.assert(!returnValueProperty, "Shouldn't have returnValueProperty here");
|
|
628
|
+
newNodes.push(createStatement(createAssignment(assignments[0].name, call)));
|
|
629
|
+
if (range.facts & RangeFacts.HasReturn) {
|
|
630
|
+
newNodes.push(createReturn());
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
// emit e.g.
|
|
635
|
+
// { a, b, __return } = newFunction(a, b);
|
|
636
|
+
// return __return;
|
|
637
|
+
newNodes.push(createStatement(createAssignment(createObjectLiteral(assignments), call)));
|
|
638
|
+
if (returnValueProperty) {
|
|
639
|
+
newNodes.push(createReturn(createIdentifier(returnValueProperty)));
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
if (range.facts & RangeFacts.HasReturn) {
|
|
645
|
+
newNodes.push(createReturn(call));
|
|
646
|
+
}
|
|
647
|
+
else if (isReadonlyArray(range.range)) {
|
|
648
|
+
newNodes.push(createStatement(call));
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
newNodes.push(call);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (isReadonlyArray(range.range)) {
|
|
655
|
+
changeTracker.replaceNodeRangeWithNodes(context.file, first(range.range), last(range.range), newNodes);
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
changeTracker.replaceNodeWithNodes(context.file, range.range, newNodes);
|
|
659
|
+
}
|
|
660
|
+
const edits = changeTracker.getChanges();
|
|
661
|
+
const renameRange = isReadonlyArray(range.range) ? first(range.range) : range.range;
|
|
662
|
+
// A declaração vem antes do uso, como a da constante (CK-69): o rename começa na última ocorrência, que é a chamada.
|
|
663
|
+
const renameFilename = renameRange.getSourceFile().fileName;
|
|
664
|
+
const renameLocation = getRenameLocation(edits, renameFilename, functionNameText, /*isDeclaredBeforeUse*/ true);
|
|
665
|
+
return { renameFilename, renameLocation, edits };
|
|
666
|
+
function getTypeDeepCloneUnionUndefined(typeNode) {
|
|
667
|
+
if (typeNode === undefined) {
|
|
668
|
+
return undefined;
|
|
669
|
+
}
|
|
670
|
+
const clone = getSynthesizedDeepClone(typeNode);
|
|
671
|
+
let withoutParens = clone;
|
|
672
|
+
while (isParenthesizedTypeNode(withoutParens)) {
|
|
673
|
+
withoutParens = withoutParens.type;
|
|
674
|
+
}
|
|
675
|
+
return isUnionTypeNode(withoutParens) && find(withoutParens.types, t => t.kind === SyntaxKind.UndefinedKeyword)
|
|
676
|
+
? clone
|
|
677
|
+
: createUnionTypeNode([clone, createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Result of 'extractRange' operation for a specific scope.
|
|
682
|
+
* Stores either a list of changes that should be applied to extract a range or a list of errors
|
|
683
|
+
*/
|
|
684
|
+
function extractConstantInScope(node, scope, { substitutions }, context) {
|
|
685
|
+
const checker = context.program.getTypeChecker();
|
|
686
|
+
// Make a unique name for the extracted variable. Na tag, o nome era outro para uma propriedade de classe (CK-02).
|
|
687
|
+
const file = scope.getSourceFile();
|
|
688
|
+
const localNameText = getUniqueName("newLocal", file);
|
|
689
|
+
// Na tag, o arquivo JavaScript não anotava o tipo (CK-03).
|
|
690
|
+
let variableType = !checker.isContextSensitive(node)
|
|
691
|
+
? undefined
|
|
692
|
+
: checker.typeToTypeNode(checker.getContextualType(node), scope, NodeBuilderFlags.NoTruncation);
|
|
693
|
+
let initializer = transformConstantInitializer(node, substitutions);
|
|
694
|
+
({ variableType, initializer } = transformFunctionInitializerAndType(variableType, initializer));
|
|
695
|
+
suppressLeadingAndTrailingTrivia(initializer);
|
|
696
|
+
const changeTracker = textChanges.ChangeTracker.fromContext(context);
|
|
697
|
+
// Na tag, numa classe, a constante era uma propriedade `readonly` privada, estática na região estática (CK-02).
|
|
698
|
+
const newVariableDeclaration = createVariableDeclaration(localNameText, variableType, initializer);
|
|
699
|
+
// If the node is part of an initializer in a list of variable declarations, insert a new
|
|
700
|
+
// variable declaration into the list (in case it depends on earlier ones).
|
|
701
|
+
// CONSIDER: If the declaration list isn't const, we might want to split it into multiple
|
|
702
|
+
// lists so that the newly extracted one can be const.
|
|
703
|
+
const oldVariableDeclaration = getContainingVariableDeclarationIfInList(node, scope);
|
|
704
|
+
if (oldVariableDeclaration) {
|
|
705
|
+
// Declare
|
|
706
|
+
// CONSIDER: could detect that each is on a separate line (See `extractConstant_VariableList_MultipleLines` in `extractConstants.ts`)
|
|
707
|
+
changeTracker.insertNodeBefore(context.file, oldVariableDeclaration, newVariableDeclaration);
|
|
708
|
+
// Consume
|
|
709
|
+
const localReference = createIdentifier(localNameText);
|
|
710
|
+
changeTracker.replaceNode(context.file, node, localReference);
|
|
711
|
+
}
|
|
712
|
+
else if (node.parent.kind === SyntaxKind.ExpressionStatement && scope === findAncestor(node, isScope)) {
|
|
713
|
+
// If the parent is an expression statement and the target scope is the immediately enclosing one,
|
|
714
|
+
// replace the statement with the declaration.
|
|
715
|
+
const newVariableStatement = createVariableStatement(
|
|
716
|
+
/*modifiers*/ undefined, createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const));
|
|
717
|
+
changeTracker.replaceNode(context.file, node.parent, newVariableStatement);
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
const newVariableStatement = createVariableStatement(
|
|
721
|
+
/*modifiers*/ undefined, createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const));
|
|
722
|
+
// Declare
|
|
723
|
+
const nodeToInsertBefore = getNodeToInsertConstantBefore(node, scope);
|
|
724
|
+
if (nodeToInsertBefore.pos === 0) {
|
|
725
|
+
changeTracker.insertNodeAtTopOfFile(context.file, newVariableStatement, /*blankLineBetween*/ false);
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariableStatement, /*blankLineBetween*/ false);
|
|
729
|
+
}
|
|
730
|
+
// Consume
|
|
731
|
+
if (node.parent.kind === SyntaxKind.ExpressionStatement) {
|
|
732
|
+
// If the parent is an expression statement, delete it.
|
|
733
|
+
changeTracker.delete(context.file, node.parent);
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
const localReference = createIdentifier(localNameText);
|
|
737
|
+
changeTracker.replaceNode(context.file, node, localReference);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
const edits = changeTracker.getChanges();
|
|
741
|
+
const renameFilename = node.getSourceFile().fileName;
|
|
742
|
+
const renameLocation = getRenameLocation(edits, renameFilename, localNameText, /*isDeclaredBeforeUse*/ true);
|
|
743
|
+
return { renameFilename, renameLocation, edits };
|
|
744
|
+
function transformFunctionInitializerAndType(variableType, initializer) {
|
|
745
|
+
// If no contextual type exists there is nothing to transfer to the function signature
|
|
746
|
+
if (variableType === undefined)
|
|
747
|
+
return { variableType, initializer };
|
|
748
|
+
// Only do this for arrow functions that are not generic. Na tag, também a function expression (CK-02).
|
|
749
|
+
if (!isArrowFunction(initializer) || !!initializer.typeParameters)
|
|
750
|
+
return { variableType, initializer };
|
|
751
|
+
const functionType = checker.getTypeAtLocation(node);
|
|
752
|
+
const functionSignature = singleOrUndefined(checker.getSignaturesOfType(functionType));
|
|
753
|
+
// If no function signature, maybe there was an error, do nothing
|
|
754
|
+
if (!functionSignature)
|
|
755
|
+
return { variableType, initializer };
|
|
756
|
+
// If the function signature has generic type parameters we don't attempt to move the parameters
|
|
757
|
+
if (!!functionSignature.getTypeParameters())
|
|
758
|
+
return { variableType, initializer };
|
|
759
|
+
// We add parameter types if needed
|
|
760
|
+
const parameters = [];
|
|
761
|
+
let hasAny = false;
|
|
762
|
+
for (const p of initializer.parameters) {
|
|
763
|
+
if (p.type) {
|
|
764
|
+
parameters.push(p);
|
|
765
|
+
}
|
|
766
|
+
else {
|
|
767
|
+
const paramType = checker.getTypeAtLocation(p);
|
|
768
|
+
if (paramType === checker.getAnyType())
|
|
769
|
+
hasAny = true;
|
|
770
|
+
parameters.push(updateParameter(p, p.modifiers, p.dotDotDotToken, p.name, p.questionToken, p.type || checker.typeToTypeNode(paramType, scope, NodeBuilderFlags.NoTruncation), p.initializer));
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
// If a parameter was inferred as any we skip adding function parameters at all.
|
|
774
|
+
// Turning an implicit any (which under common settings is a error) to an explicit
|
|
775
|
+
// is probably actually a worse refactor outcome.
|
|
776
|
+
if (hasAny)
|
|
777
|
+
return { variableType, initializer };
|
|
778
|
+
variableType = undefined;
|
|
779
|
+
// Na tag, a function expression ganhava também o parâmetro `this` da assinatura (CK-02).
|
|
780
|
+
initializer = updateArrowFunction(initializer, node.modifiers, initializer.typeParameters, parameters, initializer.type || checker.typeToTypeNode(functionSignature.getReturnType(), scope, NodeBuilderFlags.NoTruncation), initializer.equalsGreaterThanToken, initializer.body);
|
|
781
|
+
return { variableType, initializer };
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
function getContainingVariableDeclarationIfInList(node, scope) {
|
|
785
|
+
let prevNode;
|
|
786
|
+
while (node !== undefined && node !== scope) {
|
|
787
|
+
if (isVariableDeclaration(node) &&
|
|
788
|
+
node.initializer === prevNode &&
|
|
789
|
+
isVariableDeclarationList(node.parent) &&
|
|
790
|
+
node.parent.declarations.length > 1) {
|
|
791
|
+
return node;
|
|
792
|
+
}
|
|
793
|
+
prevNode = node;
|
|
794
|
+
node = node.parent;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
function getFirstDeclaration(type) {
|
|
798
|
+
let firstDeclaration;
|
|
799
|
+
const symbol = type.symbol;
|
|
800
|
+
if (symbol && symbol.declarations) {
|
|
801
|
+
for (const declaration of symbol.declarations) {
|
|
802
|
+
if (firstDeclaration === undefined || declaration.pos < firstDeclaration.pos) {
|
|
803
|
+
firstDeclaration = declaration;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return firstDeclaration;
|
|
808
|
+
}
|
|
809
|
+
function compareTypesByDeclarationOrder({ type: type1, declaration: declaration1 }, { type: type2, declaration: declaration2 }) {
|
|
810
|
+
return compareProperties(declaration1, declaration2, "pos", compareValues)
|
|
811
|
+
|| compareStringsCaseSensitive(type1.symbol ? type1.symbol.getName() : "", type2.symbol ? type2.symbol.getName() : "")
|
|
812
|
+
|| compareValues(type1.id, type2.id);
|
|
813
|
+
}
|
|
814
|
+
function transformFunctionBody(body, exposedVariableDeclarations, writes, substitutions, hasReturn) {
|
|
815
|
+
const hasWritesOrVariableDeclarations = writes !== undefined || exposedVariableDeclarations.length > 0;
|
|
816
|
+
if (isBlock(body) && !hasWritesOrVariableDeclarations && substitutions.size === 0) {
|
|
817
|
+
// already block, no declarations or writes to propagate back, no substitutions - can use node as is
|
|
818
|
+
return { body: createBlock(body.statements, /*multLine*/ true), returnValueProperty: undefined };
|
|
819
|
+
}
|
|
820
|
+
let returnValueProperty;
|
|
821
|
+
let ignoreReturns = false;
|
|
822
|
+
const statements = createNodeArray(isBlock(body) ? body.statements.slice(0) : [isStatement(body) ? body : createReturn(body)]);
|
|
823
|
+
// rewrite body if either there are writes that should be propagated back via return statements or there are substitutions
|
|
824
|
+
if (hasWritesOrVariableDeclarations || substitutions.size) {
|
|
825
|
+
const rewrittenStatements = visitNodes(statements, visitor).slice();
|
|
826
|
+
if (hasWritesOrVariableDeclarations && !hasReturn && isStatement(body)) {
|
|
827
|
+
// add return at the end to propagate writes back in case if control flow falls out of the function body
|
|
828
|
+
// it is ok to know that range has at least one return since it we only allow unconditional returns
|
|
829
|
+
const assignments = getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes);
|
|
830
|
+
if (assignments.length === 1) {
|
|
831
|
+
rewrittenStatements.push(createReturn(assignments[0].name));
|
|
832
|
+
}
|
|
833
|
+
else {
|
|
834
|
+
rewrittenStatements.push(createReturn(createObjectLiteral(assignments)));
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
return { body: createBlock(rewrittenStatements, /*multiLine*/ true), returnValueProperty };
|
|
838
|
+
}
|
|
839
|
+
else {
|
|
840
|
+
return { body: createBlock(statements, /*multiLine*/ true), returnValueProperty: undefined };
|
|
841
|
+
}
|
|
842
|
+
function visitor(node) {
|
|
843
|
+
if (!ignoreReturns && node.kind === SyntaxKind.ReturnStatement && hasWritesOrVariableDeclarations) {
|
|
844
|
+
const assignments = getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes);
|
|
845
|
+
if (node.expression) {
|
|
846
|
+
if (!returnValueProperty) {
|
|
847
|
+
returnValueProperty = "__return";
|
|
848
|
+
}
|
|
849
|
+
assignments.unshift(createPropertyAssignment(returnValueProperty, visitNode(node.expression, visitor)));
|
|
850
|
+
}
|
|
851
|
+
if (assignments.length === 1) {
|
|
852
|
+
return createReturn(assignments[0].name);
|
|
853
|
+
}
|
|
854
|
+
else {
|
|
855
|
+
return createReturn(createObjectLiteral(assignments));
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
else {
|
|
859
|
+
const oldIgnoreReturns = ignoreReturns;
|
|
860
|
+
ignoreReturns = ignoreReturns || isFunctionLikeDeclaration(node);
|
|
861
|
+
const substitution = substitutions.get(getNodeId(node).toString());
|
|
862
|
+
const result = substitution ? getSynthesizedDeepClone(substitution) : visitEachChild(node, visitor, nullTransformationContext);
|
|
863
|
+
ignoreReturns = oldIgnoreReturns;
|
|
864
|
+
return result;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
function transformConstantInitializer(initializer, substitutions) {
|
|
869
|
+
return substitutions.size
|
|
870
|
+
? visitor(initializer)
|
|
871
|
+
: initializer;
|
|
872
|
+
function visitor(node) {
|
|
873
|
+
const substitution = substitutions.get(getNodeId(node).toString());
|
|
874
|
+
return substitution ? getSynthesizedDeepClone(substitution) : visitEachChild(node, visitor, nullTransformationContext);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
// Na tag, também onde entrava a `function` extraída (o `getNodeToInsertFunctionBefore`, depois do trecho, antes da
|
|
878
|
+
// função seguinte do escopo, com o `getStatementsOrClassElements`), que agora entra como a constante (CK-69), e onde
|
|
879
|
+
// entrava a propriedade de uma classe (CK-02).
|
|
880
|
+
function getNodeToInsertConstantBefore(node, scope) {
|
|
881
|
+
let prevScope;
|
|
882
|
+
for (let curr = node; curr !== scope; curr = curr.parent) {
|
|
883
|
+
if (isScope(curr)) {
|
|
884
|
+
prevScope = curr;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
for (let curr = (prevScope || node).parent;; curr = curr.parent) {
|
|
888
|
+
if (isBlockLike(curr)) {
|
|
889
|
+
let prevStatement;
|
|
890
|
+
for (const statement of curr.statements) {
|
|
891
|
+
if (statement.pos > node.pos) {
|
|
892
|
+
break;
|
|
893
|
+
}
|
|
894
|
+
prevStatement = statement;
|
|
895
|
+
}
|
|
896
|
+
if (!prevStatement && isCaseClause(curr)) {
|
|
897
|
+
// We must have been in the expression of the case clause.
|
|
898
|
+
Debug.assert(isSwitchStatement(curr.parent.parent), "Grandparent isn't a switch statement");
|
|
899
|
+
return curr.parent.parent;
|
|
900
|
+
}
|
|
901
|
+
// There must be at least one statement since we started in one.
|
|
902
|
+
return Debug.checkDefined(prevStatement, "prevStatement failed to get set");
|
|
903
|
+
}
|
|
904
|
+
Debug.assert(curr !== scope, "Didn't encounter a block-like before encountering scope");
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
function getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes) {
|
|
908
|
+
const variableAssignments = map(exposedVariableDeclarations, v => createShorthandPropertyAssignment(v.symbol.name));
|
|
909
|
+
const writeAssignments = map(writes, w => createShorthandPropertyAssignment(w.symbol.name));
|
|
910
|
+
// TODO: GH#18217 `variableAssignments` not possibly undefined!
|
|
911
|
+
return variableAssignments === undefined
|
|
912
|
+
? writeAssignments
|
|
913
|
+
: writeAssignments === undefined
|
|
914
|
+
? variableAssignments
|
|
915
|
+
: variableAssignments.concat(writeAssignments);
|
|
916
|
+
}
|
|
917
|
+
function isReadonlyArray(v) {
|
|
918
|
+
return isArray(v);
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Produces a range that spans the entirety of nodes, given a selection
|
|
922
|
+
* that might start/end in the middle of nodes.
|
|
923
|
+
*
|
|
924
|
+
* For example, when the user makes a selection like this
|
|
925
|
+
* v---v
|
|
926
|
+
* var someThing = foo + bar;
|
|
927
|
+
* this returns ^-------^
|
|
928
|
+
*/
|
|
929
|
+
function getEnclosingTextRange(targetRange, sourceFile) {
|
|
930
|
+
return isReadonlyArray(targetRange.range)
|
|
931
|
+
? { pos: first(targetRange.range).getStart(sourceFile), end: last(targetRange.range).getEnd() }
|
|
932
|
+
: targetRange.range;
|
|
933
|
+
}
|
|
934
|
+
var Usage;
|
|
935
|
+
(function (Usage) {
|
|
936
|
+
// value should be passed to extracted method
|
|
937
|
+
Usage[Usage["Read"] = 1] = "Read";
|
|
938
|
+
// value should be passed to extracted method and propagated back
|
|
939
|
+
Usage[Usage["Write"] = 2] = "Write";
|
|
940
|
+
})(Usage || (Usage = {}));
|
|
941
|
+
function collectReadsAndWrites(targetRange, scopes, enclosingTextRange, sourceFile, checker, cancellationToken) {
|
|
942
|
+
const allTypeParameterUsages = createMap(); // Key is type ID
|
|
943
|
+
const usagesPerScope = [];
|
|
944
|
+
const substitutionsPerScope = [];
|
|
945
|
+
const functionErrorsPerScope = [];
|
|
946
|
+
const constantErrorsPerScope = [];
|
|
947
|
+
const visibleDeclarationsInExtractedRange = [];
|
|
948
|
+
const exposedVariableSymbolSet = createMap(); // Key is symbol ID
|
|
949
|
+
const exposedVariableDeclarations = [];
|
|
950
|
+
let firstExposedNonVariableDeclaration;
|
|
951
|
+
const expression = !isReadonlyArray(targetRange.range)
|
|
952
|
+
? targetRange.range
|
|
953
|
+
: targetRange.range.length === 1 && isExpressionStatement(targetRange.range[0])
|
|
954
|
+
? targetRange.range[0].expression
|
|
955
|
+
: undefined;
|
|
956
|
+
let expressionDiagnostic;
|
|
957
|
+
if (expression === undefined) {
|
|
958
|
+
const statements = targetRange.range;
|
|
959
|
+
const start = first(statements).getStart();
|
|
960
|
+
const end = last(statements).end;
|
|
961
|
+
expressionDiagnostic = createFileDiagnostic(sourceFile, start, end - start, Messages.expressionExpected);
|
|
962
|
+
}
|
|
963
|
+
else if (checker.getTypeAtLocation(expression).flags & (TypeFlags.Void | TypeFlags.Never)) {
|
|
964
|
+
expressionDiagnostic = createDiagnosticForNode(expression, Messages.uselessConstantType);
|
|
965
|
+
}
|
|
966
|
+
// initialize results
|
|
967
|
+
for (const scope of scopes) {
|
|
968
|
+
usagesPerScope.push({ usages: createMap(), typeParameterUsages: createMap(), substitutions: createMap() });
|
|
969
|
+
substitutionsPerScope.push(createMap());
|
|
970
|
+
// Na tag, a função se extraía para dentro de uma `function` declarada, e não de outra função; a arrow é a função
|
|
971
|
+
// que fica, e a extração para dentro dela continua recusada (CK-02).
|
|
972
|
+
functionErrorsPerScope.push(isFunctionLikeDeclaration(scope)
|
|
973
|
+
? [createDiagnosticForNode(scope, Messages.cannotExtractToOtherFunctionLike)]
|
|
974
|
+
: []);
|
|
975
|
+
// Na tag, também a classe de um arquivo JavaScript (CK-02, CK-03).
|
|
976
|
+
const constantErrors = [];
|
|
977
|
+
if (expressionDiagnostic) {
|
|
978
|
+
constantErrors.push(expressionDiagnostic);
|
|
979
|
+
}
|
|
980
|
+
if (isArrowFunction(scope) && !isBlock(scope.body)) {
|
|
981
|
+
constantErrors.push(createDiagnosticForNode(scope, Messages.cannotExtractToExpressionArrowFunction));
|
|
982
|
+
}
|
|
983
|
+
constantErrorsPerScope.push(constantErrors);
|
|
984
|
+
}
|
|
985
|
+
const seenUsages = createMap();
|
|
986
|
+
const target = isReadonlyArray(targetRange.range) ? createBlock(targetRange.range) : targetRange.range;
|
|
987
|
+
const unmodifiedNode = isReadonlyArray(targetRange.range) ? first(targetRange.range) : targetRange.range;
|
|
988
|
+
const inGenericContext = isInGenericContext(unmodifiedNode);
|
|
989
|
+
collectUsages(target);
|
|
990
|
+
// Unfortunately, this code takes advantage of the knowledge that the generated method
|
|
991
|
+
// will use the contextual type of an expression as the return type of the extracted
|
|
992
|
+
// method (and will therefore "use" all the types involved).
|
|
993
|
+
if (inGenericContext && !isReadonlyArray(targetRange.range)) {
|
|
994
|
+
const contextualType = checker.getContextualType(targetRange.range); // TODO: GH#18217
|
|
995
|
+
recordTypeParameterUsages(contextualType);
|
|
996
|
+
}
|
|
997
|
+
if (allTypeParameterUsages.size > 0) {
|
|
998
|
+
const seenTypeParameterUsages = createMap(); // Key is type ID
|
|
999
|
+
let i = 0;
|
|
1000
|
+
for (let curr = unmodifiedNode; curr !== undefined && i < scopes.length; curr = curr.parent) {
|
|
1001
|
+
if (curr === scopes[i]) {
|
|
1002
|
+
// Copy current contents of seenTypeParameterUsages into scope.
|
|
1003
|
+
seenTypeParameterUsages.forEach((typeParameter, id) => {
|
|
1004
|
+
usagesPerScope[i].typeParameterUsages.set(id, typeParameter);
|
|
1005
|
+
});
|
|
1006
|
+
i++;
|
|
1007
|
+
}
|
|
1008
|
+
// Note that we add the current node's type parameters *after* updating the corresponding scope.
|
|
1009
|
+
if (isDeclarationWithTypeParameters(curr)) {
|
|
1010
|
+
for (const typeParameterDecl of getEffectiveTypeParameterDeclarations(curr)) {
|
|
1011
|
+
const typeParameter = checker.getTypeAtLocation(typeParameterDecl);
|
|
1012
|
+
if (allTypeParameterUsages.has(typeParameter.id.toString())) {
|
|
1013
|
+
seenTypeParameterUsages.set(typeParameter.id.toString(), typeParameter);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
// If we didn't get through all the scopes, then there were some that weren't in our
|
|
1019
|
+
// parent chain (impossible at time of writing). A conservative solution would be to
|
|
1020
|
+
// copy allTypeParameterUsages into all remaining scopes.
|
|
1021
|
+
Debug.assert(i === scopes.length, "Should have iterated all scopes");
|
|
1022
|
+
}
|
|
1023
|
+
// If there are any declarations in the extracted block that are used in the same enclosing
|
|
1024
|
+
// lexical scope, we can't move the extraction "up" as those declarations will become unreachable
|
|
1025
|
+
if (visibleDeclarationsInExtractedRange.length) {
|
|
1026
|
+
const containingLexicalScopeOfExtraction = isBlockScope(scopes[0], scopes[0].parent)
|
|
1027
|
+
? scopes[0]
|
|
1028
|
+
: getEnclosingBlockScopeContainer(scopes[0]);
|
|
1029
|
+
forEachChild(containingLexicalScopeOfExtraction, checkForUsedDeclarations);
|
|
1030
|
+
}
|
|
1031
|
+
for (let i = 0; i < scopes.length; i++) {
|
|
1032
|
+
const scopeUsages = usagesPerScope[i];
|
|
1033
|
+
// Special case: in the innermost scope, all usages are available.
|
|
1034
|
+
// (The computed value reflects the value at the top-level of the scope, but the
|
|
1035
|
+
// local will actually be declared at the same level as the extracted expression).
|
|
1036
|
+
if (i > 0 && (scopeUsages.usages.size > 0 || scopeUsages.typeParameterUsages.size > 0)) {
|
|
1037
|
+
const errorNode = isReadonlyArray(targetRange.range) ? targetRange.range[0] : targetRange.range;
|
|
1038
|
+
constantErrorsPerScope[i].push(createDiagnosticForNode(errorNode, Messages.cannotAccessVariablesFromNestedScopes));
|
|
1039
|
+
}
|
|
1040
|
+
// Na tag, também a escrita numa propriedade `readonly` de uma classe, que só o construtor inicializa (CK-02).
|
|
1041
|
+
let hasWrite = false;
|
|
1042
|
+
usagesPerScope[i].usages.forEach(value => {
|
|
1043
|
+
if (value.usage === 2 /* Usage.Write */) {
|
|
1044
|
+
hasWrite = true;
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
1047
|
+
// If an expression was extracted, then there shouldn't have been any variable declarations.
|
|
1048
|
+
Debug.assert(isReadonlyArray(targetRange.range) || exposedVariableDeclarations.length === 0, "No variable declarations expected if something was extracted");
|
|
1049
|
+
if (hasWrite && !isReadonlyArray(targetRange.range)) {
|
|
1050
|
+
const diag = createDiagnosticForNode(targetRange.range, Messages.cannotWriteInExpression);
|
|
1051
|
+
functionErrorsPerScope[i].push(diag);
|
|
1052
|
+
constantErrorsPerScope[i].push(diag);
|
|
1053
|
+
}
|
|
1054
|
+
else if (firstExposedNonVariableDeclaration) {
|
|
1055
|
+
const diag = createDiagnosticForNode(firstExposedNonVariableDeclaration, Messages.cannotExtractExportedEntity);
|
|
1056
|
+
functionErrorsPerScope[i].push(diag);
|
|
1057
|
+
constantErrorsPerScope[i].push(diag);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
return { target, usagesPerScope, functionErrorsPerScope, constantErrorsPerScope, exposedVariableDeclarations };
|
|
1061
|
+
function isInGenericContext(node) {
|
|
1062
|
+
return !!findAncestor(node, n => isDeclarationWithTypeParameters(n) && getEffectiveTypeParameterDeclarations(n).length !== 0);
|
|
1063
|
+
}
|
|
1064
|
+
function recordTypeParameterUsages(type) {
|
|
1065
|
+
// PERF: This is potentially very expensive. `type` could be a library type with
|
|
1066
|
+
// a lot of properties, each of which the walker will visit. Unfortunately, the
|
|
1067
|
+
// solution isn't as trivial as filtering to user types because of (e.g.) Array.
|
|
1068
|
+
const symbolWalker = checker.getSymbolWalker(() => (cancellationToken.throwIfCancellationRequested(), true));
|
|
1069
|
+
const { visitedTypes } = symbolWalker.walkType(type);
|
|
1070
|
+
for (const visitedType of visitedTypes) {
|
|
1071
|
+
if (visitedType.isTypeParameter()) {
|
|
1072
|
+
allTypeParameterUsages.set(visitedType.id.toString(), visitedType);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
function collectUsages(node, valueUsage = 1 /* Usage.Read */) {
|
|
1077
|
+
if (inGenericContext) {
|
|
1078
|
+
const type = checker.getTypeAtLocation(node);
|
|
1079
|
+
recordTypeParameterUsages(type);
|
|
1080
|
+
}
|
|
1081
|
+
if (isDeclaration(node) && node.symbol) {
|
|
1082
|
+
visibleDeclarationsInExtractedRange.push(node);
|
|
1083
|
+
}
|
|
1084
|
+
if (isAssignmentExpression(node)) {
|
|
1085
|
+
// use 'write' as default usage for values
|
|
1086
|
+
collectUsages(node.left, 2 /* Usage.Write */);
|
|
1087
|
+
collectUsages(node.right);
|
|
1088
|
+
}
|
|
1089
|
+
else if (isUnaryExpressionWithWrite(node)) {
|
|
1090
|
+
collectUsages(node.operand, 2 /* Usage.Write */);
|
|
1091
|
+
}
|
|
1092
|
+
else if (isPropertyAccessExpression(node) || isElementAccessExpression(node)) {
|
|
1093
|
+
// use 'write' as default usage for values
|
|
1094
|
+
forEachChild(node, collectUsages);
|
|
1095
|
+
}
|
|
1096
|
+
else if (isIdentifier(node)) {
|
|
1097
|
+
if (!node.parent) {
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
if (isQualifiedName(node.parent) && node !== node.parent.left) {
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
if (isPropertyAccessExpression(node.parent) && node !== node.parent.expression) {
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
recordUsage(node, valueUsage, /*isTypeNode*/ isPartOfTypeNode(node));
|
|
1107
|
+
}
|
|
1108
|
+
else {
|
|
1109
|
+
forEachChild(node, collectUsages);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
function recordUsage(n, usage, isTypeNode) {
|
|
1113
|
+
const symbolId = recordUsagebySymbol(n, usage, isTypeNode);
|
|
1114
|
+
if (symbolId) {
|
|
1115
|
+
for (let i = 0; i < scopes.length; i++) {
|
|
1116
|
+
// push substitution from map<symbolId, subst> to map<nodeId, subst> to simplify rewriting
|
|
1117
|
+
const substitution = substitutionsPerScope[i].get(symbolId);
|
|
1118
|
+
if (substitution) {
|
|
1119
|
+
usagesPerScope[i].substitutions.set(getNodeId(n).toString(), substitution);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
function recordUsagebySymbol(identifier, usage, isTypeName) {
|
|
1125
|
+
const symbol = getSymbolReferencedByIdentifier(identifier);
|
|
1126
|
+
if (!symbol) {
|
|
1127
|
+
// cannot find symbol - do nothing
|
|
1128
|
+
return undefined;
|
|
1129
|
+
}
|
|
1130
|
+
const symbolId = getSymbolId(symbol).toString();
|
|
1131
|
+
const lastUsage = seenUsages.get(symbolId);
|
|
1132
|
+
// there are two kinds of value usages
|
|
1133
|
+
// - reads - if range contains a read from the value located outside of the range then value should be passed as a parameter
|
|
1134
|
+
// - writes - if range contains a write to a value located outside the range the value should be passed as a parameter and
|
|
1135
|
+
// returned as a return value
|
|
1136
|
+
// 'write' case is a superset of 'read' so if we already have processed 'write' of some symbol there is not need to handle 'read'
|
|
1137
|
+
// since all information is already recorded
|
|
1138
|
+
if (lastUsage && lastUsage >= usage) {
|
|
1139
|
+
return symbolId;
|
|
1140
|
+
}
|
|
1141
|
+
seenUsages.set(symbolId, usage);
|
|
1142
|
+
if (lastUsage) {
|
|
1143
|
+
// if we get here this means that we are trying to handle 'write' and 'read' was already processed
|
|
1144
|
+
// walk scopes and update existing records.
|
|
1145
|
+
for (const perScope of usagesPerScope) {
|
|
1146
|
+
const prevEntry = perScope.usages.get(identifier.text);
|
|
1147
|
+
if (prevEntry) {
|
|
1148
|
+
perScope.usages.set(identifier.text, { usage, symbol, node: identifier });
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return symbolId;
|
|
1152
|
+
}
|
|
1153
|
+
// find first declaration in this file
|
|
1154
|
+
const decls = symbol.getDeclarations();
|
|
1155
|
+
const declInFile = decls && find(decls, d => d.getSourceFile() === sourceFile);
|
|
1156
|
+
if (!declInFile) {
|
|
1157
|
+
return undefined;
|
|
1158
|
+
}
|
|
1159
|
+
if (rangeContainsStartEnd(enclosingTextRange, declInFile.getStart(), declInFile.end)) {
|
|
1160
|
+
// declaration is located in range to be extracted - do nothing
|
|
1161
|
+
return undefined;
|
|
1162
|
+
}
|
|
1163
|
+
// Na tag, a escrita numa referência de fora, num trecho extraído para um generator, era recusada (CK-02).
|
|
1164
|
+
for (let i = 0; i < scopes.length; i++) {
|
|
1165
|
+
const scope = scopes[i];
|
|
1166
|
+
const resolvedSymbol = checker.resolveName(symbol.name, scope, symbol.flags, /*excludeGlobals*/ false);
|
|
1167
|
+
if (resolvedSymbol === symbol) {
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
if (!substitutionsPerScope[i].has(symbolId)) {
|
|
1171
|
+
const substitution = tryReplaceWithQualifiedNameOrPropertyAccess(symbol.exportSymbol || symbol, scope, isTypeName);
|
|
1172
|
+
if (substitution) {
|
|
1173
|
+
substitutionsPerScope[i].set(symbolId, substitution);
|
|
1174
|
+
}
|
|
1175
|
+
else if (isTypeName) {
|
|
1176
|
+
// If the symbol is a type parameter that won't be in scope, we'll pass it as a type argument
|
|
1177
|
+
// so there's no problem.
|
|
1178
|
+
if (!(symbol.flags & SymbolFlags.TypeParameter)) {
|
|
1179
|
+
const diag = createDiagnosticForNode(identifier, Messages.typeWillNotBeVisibleInTheNewScope);
|
|
1180
|
+
functionErrorsPerScope[i].push(diag);
|
|
1181
|
+
constantErrorsPerScope[i].push(diag);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
else {
|
|
1185
|
+
usagesPerScope[i].usages.set(identifier.text, { usage, symbol, node: identifier });
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
return symbolId;
|
|
1190
|
+
}
|
|
1191
|
+
function checkForUsedDeclarations(node) {
|
|
1192
|
+
// If this node is entirely within the original extraction range, we don't need to do anything.
|
|
1193
|
+
if (node === targetRange.range || (isReadonlyArray(targetRange.range) && targetRange.range.indexOf(node) >= 0)) {
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
// Otherwise check and recurse.
|
|
1197
|
+
const sym = isIdentifier(node)
|
|
1198
|
+
? getSymbolReferencedByIdentifier(node)
|
|
1199
|
+
: checker.getSymbolAtLocation(node);
|
|
1200
|
+
if (sym) {
|
|
1201
|
+
const decl = find(visibleDeclarationsInExtractedRange, d => d.symbol === sym);
|
|
1202
|
+
if (decl) {
|
|
1203
|
+
if (isVariableDeclaration(decl)) {
|
|
1204
|
+
const idString = decl.symbol.id.toString();
|
|
1205
|
+
if (!exposedVariableSymbolSet.has(idString)) {
|
|
1206
|
+
exposedVariableDeclarations.push(decl);
|
|
1207
|
+
exposedVariableSymbolSet.set(idString, true);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
else {
|
|
1211
|
+
// CONSIDER: this includes binding elements, which we could
|
|
1212
|
+
// expose in the same way as variables.
|
|
1213
|
+
firstExposedNonVariableDeclaration = firstExposedNonVariableDeclaration || decl;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
forEachChild(node, checkForUsedDeclarations);
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Return the symbol referenced by an identifier (even if it declares a different symbol).
|
|
1221
|
+
*/
|
|
1222
|
+
function getSymbolReferencedByIdentifier(identifier) {
|
|
1223
|
+
// If the identifier is both a property name and its value, we're only interested in its value
|
|
1224
|
+
// (since the name is a declaration and will be included in the extracted range).
|
|
1225
|
+
return identifier.parent && isShorthandPropertyAssignment(identifier.parent) && identifier.parent.name === identifier
|
|
1226
|
+
? checker.getShorthandAssignmentValueSymbol(identifier.parent)
|
|
1227
|
+
: checker.getSymbolAtLocation(identifier);
|
|
1228
|
+
}
|
|
1229
|
+
function tryReplaceWithQualifiedNameOrPropertyAccess(symbol, scopeDecl, isTypeNode) {
|
|
1230
|
+
if (!symbol) {
|
|
1231
|
+
return undefined;
|
|
1232
|
+
}
|
|
1233
|
+
const decls = symbol.getDeclarations();
|
|
1234
|
+
if (decls && decls.some(d => d.parent === scopeDecl)) {
|
|
1235
|
+
return createIdentifier(symbol.name);
|
|
1236
|
+
}
|
|
1237
|
+
const prefix = tryReplaceWithQualifiedNameOrPropertyAccess(symbol.parent, scopeDecl, isTypeNode);
|
|
1238
|
+
if (prefix === undefined) {
|
|
1239
|
+
return undefined;
|
|
1240
|
+
}
|
|
1241
|
+
return isTypeNode
|
|
1242
|
+
? createQualifiedName(prefix, createIdentifier(symbol.name))
|
|
1243
|
+
: createPropertyAccess(prefix, symbol.name);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Computes whether or not a node represents an expression in a position where it could
|
|
1248
|
+
* be extracted.
|
|
1249
|
+
* The isExpression() in utilities.ts returns some false positives we need to handle,
|
|
1250
|
+
* such as `import x from 'y'` -- the 'y' is a StringLiteral but is *not* an expression
|
|
1251
|
+
* in the sense of something that you could extract on
|
|
1252
|
+
*/
|
|
1253
|
+
function isExtractableExpression(node) {
|
|
1254
|
+
const { parent } = node;
|
|
1255
|
+
// Na tag, o valor de um membro de enum não se extraía (CK-02).
|
|
1256
|
+
switch (node.kind) {
|
|
1257
|
+
case SyntaxKind.StringLiteral:
|
|
1258
|
+
return parent.kind !== SyntaxKind.ImportDeclaration &&
|
|
1259
|
+
parent.kind !== SyntaxKind.ImportSpecifier;
|
|
1260
|
+
case SyntaxKind.SpreadElement:
|
|
1261
|
+
case SyntaxKind.ObjectBindingPattern:
|
|
1262
|
+
case SyntaxKind.BindingElement:
|
|
1263
|
+
return false;
|
|
1264
|
+
case SyntaxKind.Identifier:
|
|
1265
|
+
return parent.kind !== SyntaxKind.BindingElement &&
|
|
1266
|
+
parent.kind !== SyntaxKind.ImportSpecifier &&
|
|
1267
|
+
parent.kind !== SyntaxKind.ExportSpecifier;
|
|
1268
|
+
}
|
|
1269
|
+
return true;
|
|
1270
|
+
}
|
|
1271
|
+
function isBlockLike(node) {
|
|
1272
|
+
switch (node.kind) {
|
|
1273
|
+
case SyntaxKind.Block:
|
|
1274
|
+
case SyntaxKind.SourceFile:
|
|
1275
|
+
case SyntaxKind.ModuleBlock:
|
|
1276
|
+
case SyntaxKind.CaseClause:
|
|
1277
|
+
return true;
|
|
1278
|
+
default:
|
|
1279
|
+
return false;
|
|
1280
|
+
}
|
|
1281
|
+
}
|