Skip to content

Commit 7d96672

Browse files
committed
fix(42605): make Convert default export to named export refactoring available for export default identifier
1 parent 26bbdf1 commit 7d96672

File tree

6 files changed

+88
-8
lines changed

6 files changed

+88
-8
lines changed

src/compiler/checker.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39739,8 +39739,11 @@ namespace ts {
3973939739
if (container.kind === SyntaxKind.ModuleDeclaration && !isAmbientModule(container)) {
3974039740
return grammarErrorOnNode(modifier, Diagnostics.A_default_export_can_only_be_used_in_an_ECMAScript_style_module);
3974139741
}
39742-
3974339742
flags |= ModifierFlags.Default;
39743+
const expression = (node as ExportAssignment).expression;
39744+
if (flags === ModifierFlags.ExportDefault && expression && expression.kind === SyntaxKind.Identifier) {
39745+
return true;
39746+
}
3974439747
break;
3974539748
case SyntaxKind.DeclareKeyword:
3974639749
if (flags & ModifierFlags.Ambient) {

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.
@@ -7133,7 +7136,9 @@ namespace ts {
71337136
isExportEquals = true;
71347137
}
71357138
else {
7136-
parseExpected(SyntaxKind.DefaultKeyword);
7139+
if (token() !== SyntaxKind.Identifier) {
7140+
parseExpected(SyntaxKind.DefaultKeyword);
7141+
}
71377142
}
71387143
const expression = parseAssignmentExpressionOrHigher();
71397144
parseSemicolon();

src/compiler/utilities.ts

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

4821+
export function isExportDefaultModifiers(modifiers: NodeArray<Modifier> | undefined): boolean{
4822+
return modifiers &&
4823+
modifiers.length === 2 &&
4824+
modifiers[0].kind === SyntaxKind.ExportKeyword &&
4825+
modifiers[1].kind === SyntaxKind.DefaultKeyword
4826+
? true
4827+
: false;
4828+
}
4829+
48214830
export function getFirstIdentifier(node: EntityNameOrEntityNameExpression): Identifier {
48224831
switch (node.kind) {
48234832
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 &&
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)