Merge branch 'master' into documentRegistery

This commit is contained in:
Sheetal Nandi
2018-05-21 12:27:12 -07:00
195 changed files with 3699 additions and 2084 deletions
+3 -2
View File
@@ -706,16 +706,17 @@ namespace ts {
break;
case SyntaxKind.JSDocTemplateTag:
processJSDocTemplateTag(<JSDocTemplateTag>tag);
pos = tag.end;
break;
case SyntaxKind.JSDocTypeTag:
processElement((<JSDocTypeTag>tag).typeExpression);
pos = tag.end;
break;
case SyntaxKind.JSDocReturnTag:
processElement((<JSDocReturnTag>tag).typeExpression);
pos = tag.end;
break;
}
pos = tag.end;
}
}
@@ -43,7 +43,7 @@ namespace ts.codefix {
if (isFunctionLikeDeclaration(decl) && (getJSDocReturnType(decl) || decl.parameters.some(p => !!getJSDocType(p)))) {
if (!decl.typeParameters) {
const typeParameters = getJSDocTypeParameterDeclarations(decl);
if (typeParameters) changes.insertTypeParameters(sourceFile, decl, typeParameters);
if (typeParameters.length) changes.insertTypeParameters(sourceFile, decl, typeParameters);
}
const needParens = isArrowFunction(decl) && !findChildOfKind(decl, SyntaxKind.OpenParenToken, sourceFile);
if (needParens) changes.insertNodeBefore(sourceFile, first(decl.parameters), createToken(SyntaxKind.OpenParenToken));
@@ -7,10 +7,8 @@ namespace ts.codefix {
errorCodes,
getCodeActions: context => {
const { sourceFile, span } = context;
const typeContainer = getImportTypeNode(sourceFile, span.start);
if (!typeContainer) return undefined;
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, typeContainer));
const importType = getImportTypeNode(sourceFile, span.start);
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, importType));
return [createCodeFixAction(fixId, changes, Diagnostics.Add_missing_typeof, fixId, Diagnostics.Add_missing_typeof)];
},
fixIds: [fixId],
@@ -18,15 +16,15 @@ namespace ts.codefix {
doChange(changes, context.sourceFile, getImportTypeNode(diag.file, diag.start!))),
});
function getImportTypeNode(sourceFile: SourceFile, pos: number): ImportTypeNode | undefined {
function getImportTypeNode(sourceFile: SourceFile, pos: number): ImportTypeNode {
const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false);
Debug.assert(token.kind === SyntaxKind.ImportKeyword);
Debug.assert(token.parent.kind === SyntaxKind.ImportType);
return <ImportTypeNode>token.parent;
}
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, typeContainer: ImportTypeNode) {
const newTypeNode = updateImportTypeNode(typeContainer, typeContainer.argument, typeContainer.qualifier, typeContainer.typeArguments, /* isTypeOf */ true);
changes.replaceNode(sourceFile, typeContainer, newTypeNode);
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, importType: ImportTypeNode) {
const newTypeNode = updateImportTypeNode(importType, importType.argument, importType.qualifier, importType.typeArguments, /* isTypeOf */ true);
changes.replaceNode(sourceFile, importType, newTypeNode);
}
}
+1 -14
View File
@@ -71,19 +71,6 @@ namespace ts.codefix {
// Calls 'cb' with the start and end of each range where 'pred' is true.
function split<T>(arr: ReadonlyArray<T>, pred: (t: T) => boolean, cb: (start: T, end: T) => void): void {
let start: T | undefined;
for (let i = 0; i < arr.length; i++) {
const value = arr[i];
if (pred(value)) {
start = start || value;
}
else {
if (start) {
cb(start, arr[i - 1]);
start = undefined;
}
}
}
if (start) cb(start, arr[arr.length - 1]);
getRangesWhere(arr, pred, (start, afterEnd) => cb(arr[start], arr[afterEnd - 1]));
}
}
+67 -37
View File
@@ -19,7 +19,7 @@ namespace ts.codefix {
const changes = textChanges.ChangeTracker.with(context, t => t.deleteNode(sourceFile, importDecl));
return [createCodeFixAction(fixName, changes, [Diagnostics.Remove_import_from_0, showModuleSpecifier(importDecl)], fixIdDelete, Diagnostics.Delete_all_unused_declarations)];
}
const delDestructure = textChanges.ChangeTracker.with(context, t => tryDeleteFullDestructure(t, sourceFile, context.span.start));
const delDestructure = textChanges.ChangeTracker.with(context, t => tryDeleteFullDestructure(t, sourceFile, context.span.start, /*deleted*/ undefined));
if (delDestructure.length) {
return [createCodeFixAction(fixName, delDestructure, Diagnostics.Remove_destructuring, fixIdDelete, Diagnostics.Delete_all_unused_declarations)];
}
@@ -27,7 +27,7 @@ namespace ts.codefix {
const token = getToken(sourceFile, textSpanEnd(context.span));
const result: CodeFixAction[] = [];
const deletion = textChanges.ChangeTracker.with(context, t => tryDeleteDeclaration(t, sourceFile, token));
const deletion = textChanges.ChangeTracker.with(context, t => tryDeleteDeclaration(t, sourceFile, token, /*deleted*/ undefined));
if (deletion.length) {
result.push(createCodeFixAction(fixName, deletion, [Diagnostics.Remove_declaration_for_Colon_0, token.getText(sourceFile)], fixIdDelete, Diagnostics.Delete_all_unused_declarations));
}
@@ -40,30 +40,37 @@ namespace ts.codefix {
return result;
},
fixIds: [fixIdPrefix, fixIdDelete],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
const { sourceFile } = context;
const token = findPrecedingToken(textSpanEnd(diag), diag.file!);
switch (context.fixId) {
case fixIdPrefix:
if (isIdentifier(token) && canPrefix(token)) {
tryPrefixDeclaration(changes, diag.code, sourceFile, token);
}
break;
case fixIdDelete:
const importDecl = tryGetFullImport(diag.file!, diag.start!);
if (importDecl) {
changes.deleteNode(sourceFile, importDecl);
}
else {
if (!tryDeleteFullDestructure(changes, sourceFile, diag.start!)) {
tryDeleteDeclaration(changes, sourceFile, token);
getAllCodeActions: context => {
// Track a set of deleted nodes that may be ancestors of other marked for deletion -- only delete the ancestors.
const deleted = new NodeSet();
return codeFixAll(context, errorCodes, (changes, diag) => {
const { sourceFile } = context;
const token = findPrecedingToken(textSpanEnd(diag), diag.file!);
switch (context.fixId) {
case fixIdPrefix:
if (isIdentifier(token) && canPrefix(token)) {
tryPrefixDeclaration(changes, diag.code, sourceFile, token);
}
}
break;
default:
Debug.fail(JSON.stringify(context.fixId));
}
}),
break;
case fixIdDelete:
// Ignore if this range was already deleted.
if (deleted.some(d => rangeContainsPosition(d, diag.start!))) break;
const importDecl = tryGetFullImport(diag.file!, diag.start!);
if (importDecl) {
changes.deleteNode(sourceFile, importDecl);
}
else {
if (!tryDeleteFullDestructure(changes, sourceFile, diag.start!, deleted)) {
tryDeleteDeclaration(changes, sourceFile, token, deleted);
}
}
break;
default:
Debug.fail(JSON.stringify(context.fixId));
}
});
},
});
// Sometimes the diagnostic span is an entire ImportDeclaration, so we should remove the whole thing.
@@ -72,18 +79,20 @@ namespace ts.codefix {
return startToken.kind === SyntaxKind.ImportKeyword ? tryCast(startToken.parent, isImportDeclaration) : undefined;
}
function tryDeleteFullDestructure(changes: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number): boolean {
function tryDeleteFullDestructure(changes: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number, deletedAncestors: NodeSet | undefined): boolean {
const startToken = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false);
if (startToken.kind !== SyntaxKind.OpenBraceToken || !isObjectBindingPattern(startToken.parent)) return false;
const decl = startToken.parent.parent;
switch (decl.kind) {
case SyntaxKind.VariableDeclaration:
tryDeleteVariableDeclaration(changes, sourceFile, decl);
tryDeleteVariableDeclaration(changes, sourceFile, decl, deletedAncestors);
break;
case SyntaxKind.Parameter:
if (deletedAncestors) deletedAncestors.add(decl);
changes.deleteNodeInList(sourceFile, decl);
break;
case SyntaxKind.BindingElement:
if (deletedAncestors) deletedAncestors.add(decl);
changes.deleteNode(sourceFile, decl);
break;
default:
@@ -121,41 +130,45 @@ namespace ts.codefix {
return false;
}
function tryDeleteDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node): void {
function tryDeleteDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, deletedAncestors: NodeSet | undefined): void {
switch (token.kind) {
case SyntaxKind.Identifier:
tryDeleteIdentifier(changes, sourceFile, <Identifier>token);
tryDeleteIdentifier(changes, sourceFile, <Identifier>token, deletedAncestors);
break;
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.NamespaceImport:
if (deletedAncestors) deletedAncestors.add(token.parent);
changes.deleteNode(sourceFile, token.parent);
break;
default:
tryDeleteDefault(changes, sourceFile, token);
tryDeleteDefault(changes, sourceFile, token, deletedAncestors);
}
}
function tryDeleteDefault(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node): void {
function tryDeleteDefault(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, deletedAncestors: NodeSet | undefined): void {
if (isDeclarationName(token)) {
if (deletedAncestors) deletedAncestors.add(token.parent);
changes.deleteNode(sourceFile, token.parent);
}
else if (isLiteralComputedPropertyDeclarationName(token)) {
if (deletedAncestors) deletedAncestors.add(token.parent.parent);
changes.deleteNode(sourceFile, token.parent.parent);
}
}
function tryDeleteIdentifier(changes: textChanges.ChangeTracker, sourceFile: SourceFile, identifier: Identifier): void {
function tryDeleteIdentifier(changes: textChanges.ChangeTracker, sourceFile: SourceFile, identifier: Identifier, deletedAncestors: NodeSet | undefined): void {
const parent = identifier.parent;
switch (parent.kind) {
case SyntaxKind.VariableDeclaration:
tryDeleteVariableDeclaration(changes, sourceFile, <VariableDeclaration>parent);
tryDeleteVariableDeclaration(changes, sourceFile, <VariableDeclaration>parent, deletedAncestors);
break;
case SyntaxKind.TypeParameter:
const typeParameters = getEffectiveTypeParameterDeclarations(<DeclarationWithTypeParameters>parent.parent);
if (typeParameters.length === 1) {
const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1, /*includeJsDocComment*/ false);
const nextToken = getTokenAtPosition(sourceFile, typeParameters.end, /*includeJsDocComment*/ false);
const { pos, end } = cast(typeParameters, isNodeArray);
const previousToken = getTokenAtPosition(sourceFile, pos - 1, /*includeJsDocComment*/ false);
const nextToken = getTokenAtPosition(sourceFile, end, /*includeJsDocComment*/ false);
Debug.assert(previousToken.kind === SyntaxKind.LessThanToken);
Debug.assert(nextToken.kind === SyntaxKind.GreaterThanToken);
@@ -255,7 +268,7 @@ namespace ts.codefix {
break;
default:
tryDeleteDefault(changes, sourceFile, identifier);
tryDeleteDefault(changes, sourceFile, identifier, deletedAncestors);
break;
}
}
@@ -280,15 +293,17 @@ namespace ts.codefix {
}
// token.parent is a variableDeclaration
function tryDeleteVariableDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, varDecl: VariableDeclaration): void {
function tryDeleteVariableDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, varDecl: VariableDeclaration, deletedAncestors: NodeSet | undefined): void {
switch (varDecl.parent.parent.kind) {
case SyntaxKind.ForStatement: {
const forStatement = varDecl.parent.parent;
const forInitializer = <VariableDeclarationList>forStatement.initializer;
if (forInitializer.declarations.length === 1) {
if (deletedAncestors) deletedAncestors.add(forInitializer);
changes.deleteNode(sourceFile, forInitializer);
}
else {
if (deletedAncestors) deletedAncestors.add(varDecl);
changes.deleteNodeInList(sourceFile, varDecl);
}
break;
@@ -298,6 +313,7 @@ namespace ts.codefix {
const forOfStatement = varDecl.parent.parent;
Debug.assert(forOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList);
const forOfInitializer = <VariableDeclarationList>forOfStatement.initializer;
if (deletedAncestors) deletedAncestors.add(forOfInitializer.declarations[0]);
changes.replaceNode(sourceFile, forOfInitializer.declarations[0], createObjectLiteral());
break;
@@ -308,11 +324,25 @@ namespace ts.codefix {
default:
const variableStatement = varDecl.parent.parent;
if (variableStatement.declarationList.declarations.length === 1) {
if (deletedAncestors) deletedAncestors.add(variableStatement);
changes.deleteNode(sourceFile, variableStatement);
}
else {
if (deletedAncestors) deletedAncestors.add(varDecl);
changes.deleteNodeInList(sourceFile, varDecl);
}
}
}
class NodeSet {
private map = createMap<Node>();
add(node: Node): void {
this.map.set(String(getNodeId(node)), node);
}
some(pred: (node: Node) => boolean): boolean {
return forEachEntry(this.map, pred) || false;
}
}
}
+25
View File
@@ -0,0 +1,25 @@
/* @internal */
namespace ts.codefix {
const fixId = "fixUnusedLabel";
const errorCodes = [Diagnostics.Unused_label.code];
registerCodeFix({
errorCodes,
getCodeActions(context) {
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, context.span.start));
return [createCodeFixAction(fixId, changes, Diagnostics.Remove_unused_label, fixId, Diagnostics.Remove_all_unused_labels)];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => doChange(changes, diag.file, diag.start)),
});
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, start: number): void {
const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
const labeledStatement = cast(token.parent, isLabeledStatement);
const pos = token.getStart(sourceFile);
const statementPos = labeledStatement.statement.getStart(sourceFile);
// If label is on a separate line, just delete the rest of that line, but not the indentation of the labeled statement.
const end = positionsAreOnSameLine(pos, statementPos, sourceFile) ? statementPos
: skipTrivia(sourceFile.text, findChildOfKind(labeledStatement, SyntaxKind.ColonToken, sourceFile)!.end, /*stopAfterLineBreak*/ true);
changes.deleteRange(sourceFile, { pos, end });
}
}
+1 -1
View File
@@ -498,7 +498,7 @@ namespace ts.codefix {
}
for (const sourceFile of allSourceFiles) {
if (isExternalOrCommonJsModule(sourceFile)) {
cb(sourceFile.symbol, sourceFile);
cb(checker.getMergedSymbol(sourceFile.symbol), sourceFile);
}
}
}
+5 -3
View File
@@ -4,7 +4,7 @@ namespace ts {
const pathUpdater = getPathUpdater(oldFilePath, newFilePath, host);
return textChanges.ChangeTracker.with({ host, formatContext }, changeTracker => {
updateTsconfigFiles(program, changeTracker, oldFilePath, newFilePath);
for (const { sourceFile, toUpdate } of getImportsToUpdate(program, oldFilePath)) {
for (const { sourceFile, toUpdate } of getImportsToUpdate(program, oldFilePath, host)) {
const newPath = pathUpdater(isRef(toUpdate) ? toUpdate.fileName : toUpdate.text);
if (newPath !== undefined) {
const range = isRef(toUpdate) ? toUpdate : createStringRange(toUpdate, sourceFile);
@@ -30,7 +30,7 @@ namespace ts {
return "fileName" in toUpdate;
}
function getImportsToUpdate(program: Program, oldFilePath: string): ReadonlyArray<ToUpdate> {
function getImportsToUpdate(program: Program, oldFilePath: string, host: LanguageServiceHost): ReadonlyArray<ToUpdate> {
const checker = program.getTypeChecker();
const result: ToUpdate[] = [];
for (const sourceFile of program.getSourceFiles()) {
@@ -44,7 +44,9 @@ namespace ts {
// If it resolved to something already, ignore.
if (checker.getSymbolAtLocation(importStringLiteral)) continue;
const resolved = program.getResolvedModuleWithFailedLookupLocationsFromCache(importStringLiteral.text, sourceFile.fileName);
const resolved = host.resolveModuleNames
? host.getResolvedModuleWithFailedLookupLocationsFromCache && host.getResolvedModuleWithFailedLookupLocationsFromCache(importStringLiteral.text, sourceFile.fileName)
: program.getResolvedModuleWithFailedLookupLocationsFromCache(importStringLiteral.text, sourceFile.fileName);
if (resolved && contains(resolved.failedLookupLocations, oldFilePath)) {
result.push({ sourceFile, toUpdate: importStringLiteral });
}
+94 -11
View File
@@ -17,19 +17,32 @@ namespace ts.OrganizeImports {
const changeTracker = textChanges.ChangeTracker.fromContext({ host, formatContext });
const coalesceAndOrganizeImports = (importGroup: ReadonlyArray<ImportDeclaration>) => coalesceImports(removeUnusedImports(importGroup, sourceFile, program));
// All of the old ImportDeclarations in the file, in syntactic order.
const topLevelImportDecls = sourceFile.statements.filter(isImportDeclaration);
organizeImportsWorker(topLevelImportDecls);
organizeImportsWorker(topLevelImportDecls, coalesceAndOrganizeImports);
// All of the old ExportDeclarations in the file, in syntactic order.
const topLevelExportDecls = sourceFile.statements.filter(isExportDeclaration);
organizeImportsWorker(topLevelExportDecls, coalesceExports);
for (const ambientModule of sourceFile.statements.filter(isAmbientModule)) {
const ambientModuleBody = getModuleBlock(ambientModule as ModuleDeclaration);
const ambientModuleImportDecls = ambientModuleBody.statements.filter(isImportDeclaration);
organizeImportsWorker(ambientModuleImportDecls);
organizeImportsWorker(ambientModuleImportDecls, coalesceAndOrganizeImports);
const ambientModuleExportDecls = ambientModuleBody.statements.filter(isExportDeclaration);
organizeImportsWorker(ambientModuleExportDecls, coalesceExports);
}
return changeTracker.getChanges();
function organizeImportsWorker(oldImportDecls: ReadonlyArray<ImportDeclaration>) {
function organizeImportsWorker<T extends ImportDeclaration | ExportDeclaration>(
oldImportDecls: ReadonlyArray<T>,
coalesce: (group: ReadonlyArray<T>) => ReadonlyArray<T>) {
if (length(oldImportDecls) === 0) {
return;
}
@@ -45,7 +58,7 @@ namespace ts.OrganizeImports {
const sortedImportGroups = stableSort(oldImportGroups, (group1, group2) => compareModuleSpecifiers(group1[0].moduleSpecifier, group2[0].moduleSpecifier));
const newImportDecls = flatMap(sortedImportGroups, importGroup =>
getExternalModuleName(importGroup[0].moduleSpecifier)
? coalesceImports(removeUnusedImports(importGroup, sourceFile, program))
? coalesce(importGroup)
: importGroup);
// Delete or replace the first import.
@@ -131,7 +144,9 @@ namespace ts.OrganizeImports {
}
function getExternalModuleName(specifier: Expression) {
return isStringLiteralLike(specifier) ? specifier.text : undefined;
return specifier !== undefined && isStringLiteralLike(specifier)
? specifier.text
: undefined;
}
/* @internal */ // Internal for testing
@@ -189,9 +204,7 @@ namespace ts.OrganizeImports {
newImportSpecifiers.push(...flatMap(namedImports, i => (i.importClause.namedBindings as NamedImports).elements));
const sortedImportSpecifiers = stableSort(newImportSpecifiers, (s1, s2) =>
compareIdentifiers(s1.propertyName || s1.name, s2.propertyName || s2.name) ||
compareIdentifiers(s1.name, s2.name));
const sortedImportSpecifiers = sortSpecifiers(newImportSpecifiers);
const importDecl = defaultImports.length > 0
? defaultImports[0]
@@ -254,9 +267,69 @@ namespace ts.OrganizeImports {
namedImports,
};
}
}
function compareIdentifiers(s1: Identifier, s2: Identifier) {
return compareStringsCaseInsensitive(s1.text, s2.text);
/* @internal */ // Internal for testing
/**
* @param exportGroup a list of ExportDeclarations, all with the same module name.
*/
export function coalesceExports(exportGroup: ReadonlyArray<ExportDeclaration>) {
if (exportGroup.length === 0) {
return exportGroup;
}
const { exportWithoutClause, namedExports } = getCategorizedExports(exportGroup);
const coalescedExports: ExportDeclaration[] = [];
if (exportWithoutClause) {
coalescedExports.push(exportWithoutClause);
}
if (namedExports.length === 0) {
return coalescedExports;
}
const newExportSpecifiers: ExportSpecifier[] = [];
newExportSpecifiers.push(...flatMap(namedExports, i => (i.exportClause).elements));
const sortedExportSpecifiers = sortSpecifiers(newExportSpecifiers);
const exportDecl = namedExports[0];
coalescedExports.push(
updateExportDeclaration(
exportDecl,
exportDecl.decorators,
exportDecl.modifiers,
updateNamedExports(exportDecl.exportClause, sortedExportSpecifiers),
exportDecl.moduleSpecifier));
return coalescedExports;
/*
* Returns entire export declarations because they may already have been rewritten and
* may lack parent pointers. The desired parts can easily be recovered based on the
* categorization.
*/
function getCategorizedExports(exportGroup: ReadonlyArray<ExportDeclaration>) {
let exportWithoutClause: ExportDeclaration | undefined;
const namedExports: ExportDeclaration[] = [];
for (const exportDeclaration of exportGroup) {
if (exportDeclaration.exportClause === undefined) {
// Only the first such export is interesting - the others are redundant.
// Note: Unfortunately, we will lose trivia that was on this node.
exportWithoutClause = exportWithoutClause || exportDeclaration;
}
else {
namedExports.push(exportDeclaration);
}
}
return {
exportWithoutClause,
namedExports,
};
}
}
@@ -273,6 +346,12 @@ namespace ts.OrganizeImports {
importDeclaration.moduleSpecifier);
}
function sortSpecifiers<T extends ImportOrExportSpecifier>(specifiers: ReadonlyArray<T>) {
return stableSort(specifiers, (s1, s2) =>
compareIdentifiers(s1.propertyName || s1.name, s2.propertyName || s2.name) ||
compareIdentifiers(s1.name, s2.name));
}
/* internal */ // Exported for testing
export function compareModuleSpecifiers(m1: Expression, m2: Expression) {
const name1 = getExternalModuleName(m1);
@@ -281,4 +360,8 @@ namespace ts.OrganizeImports {
compareBooleans(isExternalModuleNameRelative(name1), isExternalModuleNameRelative(name2)) ||
compareStringsCaseInsensitive(name1, name2);
}
}
function compareIdentifiers(s1: Identifier, s2: Identifier) {
return compareStringsCaseInsensitive(s1.text, s2.text);
}
}
+2 -14
View File
@@ -1464,7 +1464,7 @@ namespace ts.refactor.extractSymbol {
}
// Note that we add the current node's type parameters *after* updating the corresponding scope.
if (isDeclarationWithTypeParameters(curr) && getEffectiveTypeParameterDeclarations(curr)) {
if (isDeclarationWithTypeParameters(curr)) {
for (const typeParameterDecl of getEffectiveTypeParameterDeclarations(curr)) {
const typeParameter = checker.getTypeAtLocation(typeParameterDecl) as TypeParameter;
if (allTypeParameterUsages.has(typeParameter.id.toString())) {
@@ -1534,20 +1534,8 @@ namespace ts.refactor.extractSymbol {
return { target, usagesPerScope, functionErrorsPerScope, constantErrorsPerScope, exposedVariableDeclarations };
function hasTypeParameters(node: Node) {
return isDeclarationWithTypeParameters(node) &&
getEffectiveTypeParameterDeclarations(node) &&
getEffectiveTypeParameterDeclarations(node).length > 0;
}
function isInGenericContext(node: Node) {
for (; node; node = node.parent) {
if (hasTypeParameters(node)) {
return true;
}
}
return false;
return !!findAncestor(node, n => isDeclarationWithTypeParameters(n) && getEffectiveTypeParameterDeclarations(n).length !== 0);
}
function recordTypeParameterUsages(type: Type) {
@@ -21,8 +21,8 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
}
function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined {
const { file, startPosition } = context;
if (!getConvertibleFieldAtPosition(file, startPosition)) return undefined;
const { file } = context;
if (!getConvertibleFieldAtPosition(context, file)) return undefined;
return [{
name: actionName,
@@ -37,9 +37,9 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
}
function getEditsForAction(context: RefactorContext, _actionName: string): RefactorEditInfo | undefined {
const { file, startPosition } = context;
const { file } = context;
const fieldInfo = getConvertibleFieldAtPosition(file, startPosition);
const fieldInfo = getConvertibleFieldAtPosition(context, file);
if (!fieldInfo) return undefined;
const isJS = isSourceFileJavaScript(file);
@@ -117,12 +117,15 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
return name.charCodeAt(0) === CharacterCodes._;
}
function getConvertibleFieldAtPosition(file: SourceFile, startPosition: number): Info | undefined {
function getConvertibleFieldAtPosition(context: RefactorContext, file: SourceFile): Info | undefined {
const { startPosition, endPosition } = context;
const node = getTokenAtPosition(file, startPosition, /*includeJsDocComment*/ false);
const declaration = findAncestor(node.parent, isAcceptedDeclaration);
// make sure declaration have AccessibilityModifier or Static Modifier or Readonly Modifier
const meaning = ModifierFlags.AccessibilityModifier | ModifierFlags.Static | ModifierFlags.Readonly;
if (!declaration || !isConvertableName(declaration.name) || (getModifierFlags(declaration) | meaning) !== meaning) return undefined;
if (!declaration || !rangeOverlapsWithStartEnd(declaration.name, startPosition, endPosition)
|| !isConvertableName(declaration.name) || (getModifierFlags(declaration) | meaning) !== meaning) return undefined;
const name = declaration.name.text;
const startWithUnderscore = startsWithUnderscore(name);
+55 -12
View File
@@ -3,7 +3,7 @@ namespace ts.refactor {
const refactorName = "Move to a new file";
registerRefactor(refactorName, {
getAvailableActions(context): ApplicableRefactorInfo[] {
if (!context.preferences.allowTextChangesInNewFiles || getStatementsToMove(context) === undefined) return undefined;
if (!context.preferences.allowTextChangesInNewFiles || getFirstAndLastStatementToMove(context) === undefined) return undefined;
const description = getLocaleSpecificMessage(Diagnostics.Move_to_a_new_file);
return [{ name: refactorName, description, actions: [{ name: refactorName, description }] }];
},
@@ -15,7 +15,7 @@ namespace ts.refactor {
}
});
function getStatementsToMove(context: RefactorContext): ReadonlyArray<Statement> | undefined {
function getFirstAndLastStatementToMove(context: RefactorContext): { readonly first: number, readonly afterLast: number } | undefined {
const { file } = context;
const range = createTextRangeFromSpan(getRefactorContextSpan(context));
const { statements } = file;
@@ -28,12 +28,12 @@ namespace ts.refactor {
// Can't be partially into the next node
if (afterEndNodeIndex !== -1 && (afterEndNodeIndex === 0 || statements[afterEndNodeIndex].getStart(file) < range.end)) return undefined;
return statements.slice(startNodeIndex, afterEndNodeIndex === -1 ? statements.length : afterEndNodeIndex);
return { first: startNodeIndex, afterLast: afterEndNodeIndex === -1 ? statements.length : afterEndNodeIndex };
}
function doChange(oldFile: SourceFile, program: Program, toMove: ReadonlyArray<Statement>, changes: textChanges.ChangeTracker, host: LanguageServiceHost): void {
function doChange(oldFile: SourceFile, program: Program, toMove: ToMove, changes: textChanges.ChangeTracker, host: LanguageServiceHost): void {
const checker = program.getTypeChecker();
const usage = getUsageInfo(oldFile, toMove, checker);
const usage = getUsageInfo(oldFile, toMove.all, checker);
const currentDirectory = getDirectoryPath(oldFile.fileName);
const extension = extensionFromPath(oldFile.fileName);
@@ -46,6 +46,42 @@ namespace ts.refactor {
addNewFileToTsconfig(program, changes, oldFile.fileName, newFileNameWithExtension, hostGetCanonicalFileName(host));
}
interface StatementRange {
readonly first: Statement;
readonly last: Statement;
}
interface ToMove {
readonly all: ReadonlyArray<Statement>;
readonly ranges: ReadonlyArray<StatementRange>;
}
// Filters imports out of the range of statements to move. Imports will be copied to the new file anyway, and may still be needed in the old file.
function getStatementsToMove(context: RefactorContext): ToMove | undefined {
const { statements } = context.file;
const { first, afterLast } = getFirstAndLastStatementToMove(context)!;
const all: Statement[] = [];
const ranges: StatementRange[] = [];
const rangeToMove = statements.slice(first, afterLast);
getRangesWhere(rangeToMove, s => !isPureImport(s), (start, afterEnd) => {
for (let i = start; i < afterEnd; i++) all.push(rangeToMove[i]);
ranges.push({ first: rangeToMove[start], last: rangeToMove[afterEnd - 1] });
});
return { all, ranges };
}
function isPureImport(node: Node): boolean {
switch (node.kind) {
case SyntaxKind.ImportDeclaration:
return true;
case SyntaxKind.ImportEqualsDeclaration:
return !hasModifier(node, ModifierFlags.Export);
case SyntaxKind.VariableStatement:
return (node as VariableStatement).declarationList.declarations.every(d => d.initializer && isRequireCall(d.initializer, /*checkArgumentIsStringLiteralLike*/ true));
default:
return false;
}
}
function addNewFileToTsconfig(program: Program, changes: textChanges.ChangeTracker, oldFileName: string, newFileNameWithExtension: string, getCanonicalFileName: GetCanonicalFileName): void {
const cfg = program.getCompilerOptions().configFile;
if (!cfg) return;
@@ -62,13 +98,13 @@ namespace ts.refactor {
}
function getNewStatements(
oldFile: SourceFile, usage: UsageInfo, changes: textChanges.ChangeTracker, toMove: ReadonlyArray<Statement>, program: Program, newModuleName: string,
oldFile: SourceFile, usage: UsageInfo, changes: textChanges.ChangeTracker, toMove: ToMove, program: Program, newModuleName: string,
): ReadonlyArray<Statement> {
const checker = program.getTypeChecker();
if (!oldFile.externalModuleIndicator && !oldFile.commonJsModuleIndicator) {
changes.deleteNodeRange(oldFile, first(toMove), last(toMove));
return toMove;
deleteMovedStatements(oldFile, toMove.ranges, changes);
return toMove.all;
}
const useEs6ModuleSyntax = !!oldFile.externalModuleIndicator;
@@ -77,17 +113,23 @@ namespace ts.refactor {
changes.insertNodeBefore(oldFile, oldFile.statements[0], importsFromNewFile, /*blankLineBetween*/ true);
}
deleteUnusedOldImports(oldFile, toMove, changes, usage.unusedImportsFromOldFile, checker);
changes.deleteNodeRange(oldFile, first(toMove), last(toMove));
deleteUnusedOldImports(oldFile, toMove.all, changes, usage.unusedImportsFromOldFile, checker);
deleteMovedStatements(oldFile, toMove.ranges, changes);
updateImportsInOtherFiles(changes, program, oldFile, usage.movedSymbols, newModuleName);
return [
...getNewFileImportsAndAddExportInOldFile(oldFile, usage.oldImportsNeededByNewFile, usage.newFileImportsFromOldFile, changes, checker, useEs6ModuleSyntax),
...addExports(oldFile, toMove, usage.oldFileImportsFromNewFile, useEs6ModuleSyntax),
...addExports(oldFile, toMove.all, usage.oldFileImportsFromNewFile, useEs6ModuleSyntax),
];
}
function deleteMovedStatements(sourceFile: SourceFile, moved: ReadonlyArray<StatementRange>, changes: textChanges.ChangeTracker) {
for (const { first, last } of moved) {
changes.deleteNodeRange(sourceFile, first, last);
}
}
function deleteUnusedOldImports(oldFile: SourceFile, toMove: ReadonlyArray<Statement>, changes: textChanges.ChangeTracker, toDelete: ReadonlySymbolSet, checker: TypeChecker) {
for (const statement of oldFile.statements) {
if (contains(toMove, statement)) continue;
@@ -391,13 +433,14 @@ namespace ts.refactor {
}
function isVariableDeclarationInImport(decl: VariableDeclaration) {
return isSourceFile(decl.parent.parent.parent) &&
isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true);
decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true);
}
function filterImport(i: SupportedImport, moduleSpecifier: StringLiteralLike, keep: (name: Identifier) => boolean): SupportedImportStatement | undefined {
switch (i.kind) {
case SyntaxKind.ImportDeclaration: {
const clause = i.importClause;
if (!clause) return undefined;
const defaultImport = clause.name && keep(clause.name) ? clause.name : undefined;
const namedBindings = clause.namedBindings && filterNamedBindings(clause.namedBindings, keep);
return defaultImport || namedBindings
+1 -1
View File
@@ -60,7 +60,7 @@ namespace ts {
}
}
return diags.concat(checker.getSuggestionDiagnostics(sourceFile));
return diags.concat(checker.getSuggestionDiagnostics(sourceFile)).sort((d1, d2) => d1.start - d2.start);
}
// convertToEs6Module only works on top-level, so don't trigger it if commonjs code only appears in nested scopes.
+29 -30
View File
@@ -212,7 +212,7 @@ namespace ts.textChanges {
export class ChangeTracker {
private readonly changes: Change[] = [];
private readonly newFiles: { readonly oldFile: SourceFile, readonly fileName: string, readonly statements: ReadonlyArray<Statement> }[] = [];
private readonly deletedNodesInLists: true[] = []; // Stores ids of nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`.
private readonly deletedNodesInLists = new NodeSet(); // Stores ids of nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`.
private readonly classesWithNodesInsertedAtStart = createMap<ClassDeclaration>(); // Set<ClassDeclaration> implemented as Map<node id, ClassDeclaration>
public static fromContext(context: TextChangesContext): ChangeTracker {
@@ -262,35 +262,15 @@ namespace ts.textChanges {
this.deleteNode(sourceFile, node);
return this;
}
const id = getNodeId(node);
Debug.assert(!this.deletedNodesInLists[id], "Deleting a node twice");
this.deletedNodesInLists[id] = true;
if (index !== containingList.length - 1) {
const nextToken = getTokenAtPosition(sourceFile, node.end, /*includeJsDocComment*/ false);
if (nextToken && isSeparator(node, nextToken)) {
// find first non-whitespace position in the leading trivia of the node
const startPosition = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
const nextElement = containingList[index + 1];
/// find first non-whitespace position in the leading trivia of the next node
const endPosition = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, nextElement, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
// shift next node so its first non-whitespace position will be moved to the first non-whitespace position of the deleted node
this.deleteRange(sourceFile, { pos: startPosition, end: endPosition });
}
}
else {
const prev = containingList[index - 1];
if (this.deletedNodesInLists[getNodeId(prev)]) {
const pos = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
const end = getAdjustedEndPosition(sourceFile, node, {});
this.deleteRange(sourceFile, { pos, end });
}
else {
const previousToken = getTokenAtPosition(sourceFile, containingList[index - 1].end, /*includeJsDocComment*/ false);
if (previousToken && isSeparator(node, previousToken)) {
this.deleteNodeRange(sourceFile, previousToken, node);
}
}
}
// Note: We will only delete a comma *after* a node. This will leave a trailing comma if we delete the last node.
// That's handled in the end by `finishTrailingCommaAfterDeletingNodesInList`.
Debug.assert(!this.deletedNodesInLists.has(node), "Deleting a node twice");
this.deletedNodesInLists.add(node);
this.deleteRange(sourceFile, {
pos: startPositionToDeleteNodeInList(sourceFile, node),
end: index === containingList.length - 1 ? getAdjustedEndPosition(sourceFile, node, {}) : startPositionToDeleteNodeInList(sourceFile, containingList[index + 1]),
});
return this;
}
@@ -683,6 +663,19 @@ namespace ts.textChanges {
});
}
private finishTrailingCommaAfterDeletingNodesInList() {
this.deletedNodesInLists.forEach(node => {
const sourceFile = node.getSourceFile();
const list = formatting.SmartIndenter.getContainingList(node, sourceFile);
if (node !== last(list)) return;
const lastNonDeletedIndex = findLastIndex(list, n => !this.deletedNodesInLists.has(n), list.length - 2);
if (lastNonDeletedIndex !== -1) {
this.deleteRange(sourceFile, { pos: list[lastNonDeletedIndex].end, end: startPositionToDeleteNodeInList(sourceFile, list[lastNonDeletedIndex + 1]) });
}
});
}
/**
* Note: after calling this, the TextChanges object must be discarded!
* @param validate only for tests
@@ -691,6 +684,7 @@ namespace ts.textChanges {
*/
public getChanges(validate?: ValidateNonFormattedText): FileTextChanges[] {
this.finishClassesWithNodesInsertedAtStart();
this.finishTrailingCommaAfterDeletingNodesInList();
const changes = changesToText.getTextChangesFromChanges(this.changes, this.newLineCharacter, this.formatContext, validate);
for (const { oldFile, fileName, statements } of this.newFiles) {
changes.push(changesToText.newFileChanges(oldFile, fileName, statements, this.newLineCharacter));
@@ -703,6 +697,11 @@ namespace ts.textChanges {
}
}
// find first non-whitespace position in the leading trivia of the node
function startPositionToDeleteNodeInList(sourceFile: SourceFile, node: Node): number {
return skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
}
function getClassBraceEnds(cls: ClassLikeDeclaration, sourceFile: SourceFile): [number, number] {
return [findChildOfKind(cls, SyntaxKind.OpenBraceToken, sourceFile).end, findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile).end];
}
+1
View File
@@ -99,6 +99,7 @@
"codefixes/fixForgottenThisPropertyAccess.ts",
"codefixes/fixUnusedIdentifier.ts",
"codefixes/fixUnreachableCode.ts",
"codefixes/fixUnusedLabel.ts",
"codefixes/fixJSDocTypes.ts",
"codefixes/fixAwaitInSyncFunction.ts",
"codefixes/disableJsDiagnostics.ts",
+3
View File
@@ -207,8 +207,11 @@ namespace ts {
* LS host can optionally implement this method if it wants to be completely in charge of module name resolution.
* if implementation is omitted then language service will use built-in module resolution logic and get answers to
* host specific questions using 'getScriptSnapshot'.
*
* If this is implemented, `getResolvedModuleWithFailedLookupLocationsFromCache` should be too.
*/
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[];
getResolvedModuleWithFailedLookupLocationsFromCache?(modulename: string, containingFile: string): ResolvedModuleWithFailedLookupLocations;
resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
/* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution;
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean;
+21
View File
@@ -417,6 +417,10 @@ namespace ts {
return startEndContainsRange(r1.pos, r1.end, r2);
}
export function rangeContainsPosition(r: TextRange, pos: number): boolean {
return r.pos <= pos && pos <= r.end;
}
export function startEndContainsRange(start: number, end: number, range: TextRange): boolean {
return start <= range.pos && end >= range.end;
}
@@ -1276,6 +1280,23 @@ namespace ts {
}
return propSymbol;
}
export class NodeSet {
private map = createMap<Node>();
add(node: Node): void {
this.map.set(String(getNodeId(node)), node);
}
has(node: Node): boolean {
return this.map.has(String(getNodeId(node)));
}
forEach(cb: (node: Node) => void): void {
this.map.forEach(cb);
}
some(pred: (node: Node) => boolean): boolean {
return forEachEntry(this.map, pred) || false;
}
}
}
// Display-part writer helpers