mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge branch 'master' into documentRegistery
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user