Skip to content

Commit 6178b76

Browse files
author
Andy Hanson
committed
Add refactor to convert named to default export and back
1 parent c437404 commit 6178b76

18 files changed

+351
-35
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4305,5 +4305,13 @@
43054305
"Convert named imports to namespace import": {
43064306
"category": "Message",
43074307
"code": 95057
4308+
},
4309+
"Convert default export to named export": {
4310+
"category": "Message",
4311+
"code": 95058
4312+
},
4313+
"Convert named export to default export": {
4314+
"category": "Message",
4315+
"code": 95059
43084316
}
43094317
}

src/compiler/utilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5643,7 +5643,7 @@ namespace ts {
56435643
// Keywords
56445644

56455645
/* @internal */
5646-
export function isModifierKind(token: SyntaxKind): boolean {
5646+
export function isModifierKind(token: SyntaxKind): token is Modifier["kind"] {
56475647
switch (token) {
56485648
case SyntaxKind.AbstractKeyword:
56495649
case SyntaxKind.AsyncKeyword:

src/harness/fourslash.ts

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3032,7 +3032,10 @@ Actual: ${stringify(fullActual)}`);
30323032

30333033
public verifyRefactorAvailable(negative: boolean, name: string, actionName?: string) {
30343034
let refactors = this.getApplicableRefactors(this.getSelection());
3035-
refactors = refactors.filter(r => r.name === name && (actionName === undefined || r.actions.some(a => a.name === actionName)));
3035+
ts.Debug.assert(name !== undefined || negative);
3036+
if (name !== undefined) {
3037+
refactors = refactors.filter(r => r.name === name && (actionName === undefined || r.actions.some(a => a.name === actionName)));
3038+
}
30363039
const isAvailable = refactors.length > 0;
30373040

30383041
if (negative) {
@@ -3091,32 +3094,44 @@ Actual: ${stringify(fullActual)}`);
30913094
this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false);
30923095
}
30933096

3094-
const { renamePosition, newContent } = parseNewContent();
3097+
let renameFilename: string | undefined;
3098+
let renamePosition: number | undefined;
30953099

3096-
this.verifyCurrentFileContent(newContent);
3100+
const newFileContents = typeof newContentWithRenameMarker === "string" ? { [this.activeFile.fileName]: newContentWithRenameMarker } : newContentWithRenameMarker;
3101+
for (const fileName in newFileContents) {
3102+
const { renamePosition: rp, newContent } = TestState.parseNewContent(newFileContents[fileName]);
3103+
if (renamePosition === undefined) {
3104+
renameFilename = fileName;
3105+
renamePosition = rp;
3106+
}
3107+
else {
3108+
ts.Debug.assert(rp === undefined);
3109+
}
3110+
this.verifyFileContent(fileName, newContent);
3111+
3112+
}
30973113

30983114
if (renamePosition === undefined) {
30993115
if (editInfo.renameLocation !== undefined) {
31003116
this.raiseError(`Did not expect a rename location, got ${editInfo.renameLocation}`);
31013117
}
31023118
}
31033119
else {
3104-
// TODO: test editInfo.renameFilename value
3105-
assert.isDefined(editInfo.renameFilename);
3120+
this.assertObjectsEqual(editInfo.renameFilename, renameFilename);
31063121
if (renamePosition !== editInfo.renameLocation) {
31073122
this.raiseError(`Expected rename position of ${renamePosition}, but got ${editInfo.renameLocation}`);
31083123
}
31093124
}
3125+
}
31103126

3111-
function parseNewContent(): { renamePosition: number | undefined, newContent: string } {
3112-
const renamePosition = newContentWithRenameMarker.indexOf("/*RENAME*/");
3113-
if (renamePosition === -1) {
3114-
return { renamePosition: undefined, newContent: newContentWithRenameMarker };
3115-
}
3116-
else {
3117-
const newContent = newContentWithRenameMarker.slice(0, renamePosition) + newContentWithRenameMarker.slice(renamePosition + "/*RENAME*/".length);
3118-
return { renamePosition, newContent };
3119-
}
3127+
private static parseNewContent(newContentWithRenameMarker: string): { readonly renamePosition: number | undefined, readonly newContent: string } {
3128+
const renamePosition = newContentWithRenameMarker.indexOf("/*RENAME*/");
3129+
if (renamePosition === -1) {
3130+
return { renamePosition: undefined, newContent: newContentWithRenameMarker };
3131+
}
3132+
else {
3133+
const newContent = newContentWithRenameMarker.slice(0, renamePosition) + newContentWithRenameMarker.slice(renamePosition + "/*RENAME*/".length);
3134+
return { renamePosition, newContent };
31203135
}
31213136
}
31223137

@@ -4733,7 +4748,7 @@ namespace FourSlashInterface {
47334748
refactorName: string;
47344749
actionName: string;
47354750
actionDescription: string;
4736-
newContent: string;
4751+
newContent: NewFileContent;
47374752
}
47384753

47394754
export type ExpectedCompletionEntry = string | {
@@ -4795,9 +4810,11 @@ namespace FourSlashInterface {
47954810
filesToSearch?: ReadonlyArray<string>;
47964811
}
47974812

4813+
export type NewFileContent = string | { readonly [filename: string]: string };
4814+
47984815
export interface NewContentOptions {
47994816
// Exactly one of these should be defined.
4800-
newFileContent?: string | { readonly [filename: string]: string };
4817+
newFileContent?: NewFileContent;
48014818
newRangeContent?: string;
48024819
}
48034820

src/harness/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"../services/codefixes/useDefaultImport.ts",
121121
"../services/codefixes/fixAddModuleReferTypeMissingTypeof.ts",
122122
"../services/codefixes/convertToMappedObjectType.ts",
123+
"../services/refactors/convertExport.ts",
123124
"../services/refactors/convertImport.ts",
124125
"../services/refactors/extractSymbol.ts",
125126
"../services/refactors/generateGetAccessorAndSetAccessor.ts",

src/server/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@
116116
"../services/codefixes/useDefaultImport.ts",
117117
"../services/codefixes/fixAddModuleReferTypeMissingTypeof.ts",
118118
"../services/codefixes/convertToMappedObjectType.ts",
119+
"../services/refactors/convertExport.ts",
119120
"../services/refactors/convertImport.ts",
120121
"../services/refactors/extractSymbol.ts",
121122
"../services/refactors/generateGetAccessorAndSetAccessor.ts",

src/server/tsconfig.library.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
"../services/codefixes/useDefaultImport.ts",
123123
"../services/codefixes/fixAddModuleReferTypeMissingTypeof.ts",
124124
"../services/codefixes/convertToMappedObjectType.ts",
125+
"../services/refactors/convertExport.ts",
125126
"../services/refactors/convertImport.ts",
126127
"../services/refactors/extractSymbol.ts",
127128
"../services/refactors/generateGetAccessorAndSetAccessor.ts",

src/services/documentHighlights.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -187,15 +187,8 @@ namespace ts.DocumentHighlights {
187187
});
188188
}
189189

190-
function getModifierOccurrences(modifier: SyntaxKind, declaration: Node): Node[] {
191-
const modifierFlag = modifierToFlag(modifier);
192-
return mapDefined(getNodesToSearchForModifier(declaration, modifierFlag), node => {
193-
if (getModifierFlags(node) & modifierFlag) {
194-
const mod = find(node.modifiers!, m => m.kind === modifier);
195-
Debug.assert(!!mod);
196-
return mod;
197-
}
198-
});
190+
function getModifierOccurrences(modifier: Modifier["kind"], declaration: Node): Node[] {
191+
return mapDefined(getNodesToSearchForModifier(declaration, modifierToFlag(modifier)), node => findModifier(node, modifier));
199192
}
200193

201194
function getNodesToSearchForModifier(declaration: Node, modifierFlag: ModifierFlags): ReadonlyArray<Node> | undefined {

src/services/findAllReferences.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,30 @@ namespace ts.FindAllReferences.Core {
590590
}
591591
}
592592

593+
export function eachExportReference(
594+
sourceFiles: ReadonlyArray<SourceFile>,
595+
checker: TypeChecker,
596+
cancellationToken: CancellationToken | undefined,
597+
exportSymbol: Symbol,
598+
exportingModuleSymbol: Symbol,
599+
exportName: string,
600+
isDefaultExport: boolean,
601+
cb: (ref: Identifier) => void,
602+
): void {
603+
const importTracker = createImportTracker(sourceFiles, arrayToSet(sourceFiles, f => f.fileName), checker, cancellationToken);
604+
const { importSearches, indirectUsers } = importTracker(exportSymbol, { exportKind: isDefaultExport ? ExportKind.Default : ExportKind.Named, exportingModuleSymbol }, /*isForRename*/ false);
605+
for (const [importLocation] of importSearches) {
606+
cb(importLocation);
607+
}
608+
for (const indirectUser of indirectUsers) {
609+
for (const node of getPossibleSymbolReferenceNodes(indirectUser, isDefaultExport ? "default" : exportName)) {
610+
if (isIdentifier(node) && checker.getSymbolAtLocation(node) === exportSymbol) {
611+
cb(node);
612+
}
613+
}
614+
}
615+
}
616+
593617
function shouldAddSingleReference(singleRef: Identifier | StringLiteral, state: State): boolean {
594618
if (!hasMatchingMeaning(singleRef, state)) return false;
595619
if (!state.options.isForRename) return true;

src/services/importTracker.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace ts.FindAllReferences {
1212
export type ImportTracker = (exportSymbol: Symbol, exportInfo: ExportInfo, isForRename: boolean) => ImportsResult;
1313

1414
/** Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily. */
15-
export function createImportTracker(sourceFiles: ReadonlyArray<SourceFile>, sourceFilesSet: ReadonlyMap<true>, checker: TypeChecker, cancellationToken: CancellationToken): ImportTracker {
15+
export function createImportTracker(sourceFiles: ReadonlyArray<SourceFile>, sourceFilesSet: ReadonlyMap<true>, checker: TypeChecker, cancellationToken: CancellationToken | undefined): ImportTracker {
1616
const allDirectImports = getDirectImportsMap(sourceFiles, checker, cancellationToken);
1717
return (exportSymbol, exportInfo, isForRename) => {
1818
const { directImports, indirectUsers } = getImportersForExport(sourceFiles, sourceFilesSet, allDirectImports, exportInfo, checker, cancellationToken);
@@ -43,7 +43,7 @@ namespace ts.FindAllReferences {
4343
allDirectImports: Map<ImporterOrCallExpression[]>,
4444
{ exportingModuleSymbol, exportKind }: ExportInfo,
4545
checker: TypeChecker,
46-
cancellationToken: CancellationToken
46+
cancellationToken: CancellationToken | undefined,
4747
): { directImports: Importer[], indirectUsers: ReadonlyArray<SourceFile> } {
4848
const markSeenDirectImport = nodeSeenTracker<ImporterOrCallExpression>();
4949
const markSeenIndirectUser = nodeSeenTracker<SourceFileLike>();
@@ -80,7 +80,7 @@ namespace ts.FindAllReferences {
8080
continue;
8181
}
8282

83-
cancellationToken.throwIfCancellationRequested();
83+
if (cancellationToken) cancellationToken.throwIfCancellationRequested();
8484

8585
switch (direct.kind) {
8686
case SyntaxKind.CallExpression:
@@ -373,11 +373,11 @@ namespace ts.FindAllReferences {
373373
}
374374

375375
/** Returns a map from a module symbol Id to all import statements that directly reference the module. */
376-
function getDirectImportsMap(sourceFiles: ReadonlyArray<SourceFile>, checker: TypeChecker, cancellationToken: CancellationToken): Map<ImporterOrCallExpression[]> {
376+
function getDirectImportsMap(sourceFiles: ReadonlyArray<SourceFile>, checker: TypeChecker, cancellationToken: CancellationToken | undefined): Map<ImporterOrCallExpression[]> {
377377
const map = createMap<ImporterOrCallExpression[]>();
378378

379379
for (const sourceFile of sourceFiles) {
380-
cancellationToken.throwIfCancellationRequested();
380+
if (cancellationToken) cancellationToken.throwIfCancellationRequested();
381381
forEachImport(sourceFile, (importDecl, moduleSpecifier) => {
382382
const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier);
383383
if (moduleSymbol) {

0 commit comments

Comments
 (0)