Skip to content

Commit 776c8f0

Browse files
committed
fix(42605): make Convert default export to named export refactoring available for export default identifier
1 parent 4d50624 commit 776c8f0

File tree

5 files changed

+84
-7
lines changed

5 files changed

+84
-7
lines changed

src/compiler/parser.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1823,7 +1823,7 @@ namespace ts {
18231823
function nextTokenCanFollowDefaultKeyword(): boolean {
18241824
nextToken();
18251825
return token() === SyntaxKind.ClassKeyword || token() === SyntaxKind.FunctionKeyword ||
1826-
token() === SyntaxKind.InterfaceKeyword ||
1826+
token() === SyntaxKind.InterfaceKeyword || token() === SyntaxKind.Identifier ||
18271827
(token() === SyntaxKind.AbstractKeyword && lookAhead(nextTokenIsClassKeywordOnSameLine)) ||
18281828
(token() === SyntaxKind.AsyncKeyword && lookAhead(nextTokenIsFunctionKeywordOnSameLine));
18291829
}
@@ -6182,6 +6182,9 @@ namespace ts {
61826182
return parseExportDeclaration(pos, hasJSDoc, decorators, modifiers);
61836183
}
61846184
default:
6185+
if ((token() === SyntaxKind.Identifier) && isExportDefaultModifiers(modifiers)) {
6186+
return parseExportAssignment(pos, hasJSDoc, decorators, modifiers);
6187+
}
61856188
if (decorators || modifiers) {
61866189
// We reached this point because we encountered decorators and/or modifiers and assumed a declaration
61876190
// would follow. For recovery and error reporting purposes, return an incomplete declaration.
@@ -7134,7 +7137,9 @@ namespace ts {
71347137
isExportEquals = true;
71357138
}
71367139
else {
7137-
parseExpected(SyntaxKind.DefaultKeyword);
7140+
if (token() !== SyntaxKind.Identifier) {
7141+
parseExpected(SyntaxKind.DefaultKeyword);
7142+
}
71387143
}
71397144
const expression = parseAssignmentExpressionOrHigher();
71407145
parseSemicolon();

src/compiler/utilities.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4720,7 +4720,7 @@ namespace ts {
47204720
* NOTE: This function does not use `parent` pointers and will not include modifiers from JSDoc.
47214721
*/
47224722
export function getSyntacticModifierFlagsNoCache(node: Node): ModifierFlags {
4723-
let flags = modifiersToFlags(node.modifiers);
4723+
let flags = isExportAssignment(node) && isExportDefaultModifiers(node.modifiers) ? ModifierFlags.None : modifiersToFlags(node.modifiers);
47244724
if (node.flags & NodeFlags.NestedNamespace || (node.kind === SyntaxKind.Identifier && (<Identifier>node).isInJSDocNamespace)) {
47254725
flags |= ModifierFlags.Export;
47264726
}
@@ -4820,6 +4820,15 @@ namespace ts {
48204820
return node.kind === SyntaxKind.Identifier || isPropertyAccessEntityNameExpression(node);
48214821
}
48224822

4823+
export function isExportDefaultModifiers(modifiers: NodeArray<Modifier> | undefined): boolean{
4824+
return modifiers &&
4825+
modifiers.length === 2 &&
4826+
modifiers[0].kind === SyntaxKind.ExportKeyword &&
4827+
modifiers[1].kind === SyntaxKind.DefaultKeyword
4828+
? true
4829+
: false;
4830+
}
4831+
48234832
export function getFirstIdentifier(node: EntityNameOrEntityNameExpression): Identifier {
48244833
switch (node.kind) {
48254834
case SyntaxKind.Identifier:

src/services/findAllReferences.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1180,7 +1180,15 @@ namespace ts.FindAllReferences {
11801180
for (const indirectUser of indirectUsers) {
11811181
for (const node of getPossibleSymbolReferenceNodes(indirectUser, isDefaultExport ? "default" : exportName)) {
11821182
// Import specifiers should be handled by importSearches
1183-
if (isIdentifier(node) && !isImportOrExportSpecifier(node.parent) && checker.getSymbolAtLocation(node) === exportSymbol) {
1183+
const symbol = checker.getSymbolAtLocation(node);
1184+
const isExportDefaultIdentifier =
1185+
symbol && symbol.declarations &&
1186+
symbol.declarations[0] &&
1187+
isExportAssignment(symbol.declarations[0]) &&
1188+
isExportDefaultModifiers(
1189+
symbol.declarations[0].modifiers
1190+
);
1191+
if (isIdentifier(node) && !isImportOrExportSpecifier(node.parent) && (symbol === exportSymbol || isExportDefaultIdentifier)) {
11841192
cb(node);
11851193
}
11861194
}

src/services/refactors/convertExport.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ namespace ts.refactor {
4848
});
4949

5050
// If a VariableStatement, will have exactly one VariableDeclaration, with an Identifier for a name.
51-
type ExportToConvert = FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | NamespaceDeclaration | TypeAliasDeclaration | VariableStatement;
51+
type ExportToConvert = FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | NamespaceDeclaration | TypeAliasDeclaration | VariableStatement | ExportAssignment;
5252
interface ExportInfo {
5353
readonly exportNode: ExportToConvert;
5454
readonly exportName: Identifier; // This is exportNode.name except for VariableStatement_s.
@@ -67,7 +67,7 @@ namespace ts.refactor {
6767

6868
const exportingModuleSymbol = isSourceFile(exportNode.parent) ? exportNode.parent.symbol : exportNode.parent.parent.symbol;
6969

70-
const flags = getSyntacticModifierFlags(exportNode);
70+
const flags = getSyntacticModifierFlags(exportNode) || (isExportDefaultModifiers(exportNode.modifiers) ? ModifierFlags.ExportDefault : ModifierFlags.None);
7171
const wasDefault = !!(flags & ModifierFlags.Default);
7272
// If source file already has a default export, don't offer refactor.
7373
if (!(flags & ModifierFlags.Export) || !wasDefault && exportingModuleSymbol.exports!.has(InternalSymbolName.Default)) {
@@ -95,6 +95,11 @@ namespace ts.refactor {
9595
Debug.assert(!wasDefault, "Can't have a default flag here");
9696
return isIdentifier(decl.name) ? { exportNode: vs, exportName: decl.name, wasDefault, exportingModuleSymbol } : undefined;
9797
}
98+
case SyntaxKind.ExportAssignment: {
99+
const node = exportNode as ExportAssignment;
100+
const exp = node.expression as Identifier;
101+
return { exportNode: node, exportName: exp, wasDefault, exportingModuleSymbol };
102+
}
98103
default:
99104
return undefined;
100105
}
@@ -108,6 +113,11 @@ namespace ts.refactor {
108113
function changeExport(exportingSourceFile: SourceFile, { wasDefault, exportNode, exportName }: ExportInfo, changes: textChanges.ChangeTracker, checker: TypeChecker): void {
109114
if (wasDefault) {
110115
changes.delete(exportingSourceFile, Debug.checkDefined(findModifier(exportNode, SyntaxKind.DefaultKeyword), "Should find a default keyword in modifier list"));
116+
if (exportNode.kind === SyntaxKind.ExportAssignment) {
117+
const exp = exportNode.expression as Identifier;
118+
const spec = makeExportSpecifier(exp.text, exp.text);
119+
changes.replaceNode(exportingSourceFile, exp, factory.createNamedExports([spec]));
120+
}
111121
}
112122
else {
113123
const exportKeyword = Debug.checkDefined(findModifier(exportNode, SyntaxKind.ExportKeyword), "Should find an export keyword in modifier list");
@@ -134,7 +144,7 @@ namespace ts.refactor {
134144
changes.insertNodeAfter(exportingSourceFile, exportNode, factory.createExportDefault(factory.createIdentifier(exportName.text)));
135145
break;
136146
default:
137-
Debug.assertNever(exportNode, `Unexpected exportNode kind ${(exportNode as ExportToConvert).kind}`);
147+
Debug.assertNever(exportNode as never, `Unexpected exportNode kind ${(exportNode as ExportToConvert).kind}`);
138148
}
139149
}
140150
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @Filename: /a.ts
4+
////const f = () => {};
5+
/////*a*/export default f;/*b*/
6+
7+
8+
// @Filename: /b.ts
9+
////import f from "./a";
10+
////import { default as f } from "./a";
11+
////import { default as g } from "./a";
12+
////import f, * as a from "./a";
13+
////
14+
////export { default } from "./a";
15+
////export { default as f } from "./a";
16+
////export { default as i } from "./a";
17+
////
18+
////import * as a from "./a";
19+
////a.default();
20+
21+
goTo.select("a", "b");
22+
edit.applyRefactor({
23+
refactorName: "Convert export",
24+
actionName: "Convert default export to named export",
25+
actionDescription: "Convert default export to named export",
26+
newContent: {
27+
"/a.ts":
28+
`const f = () => {};
29+
export { f };`,
30+
31+
"/b.ts":
32+
`import { f } from "./a";
33+
import { f } from "./a";
34+
import { f as g } from "./a";
35+
import * as a from "./a";
36+
import { f } from "./a";
37+
38+
export { f as default } from "./a";
39+
export { f } from "./a";
40+
export { f as i } from "./a";
41+
42+
import * as a from "./a";
43+
a.f();`,
44+
},
45+
});

0 commit comments

Comments
 (0)