Skip to content

Commit 94ca501

Browse files
authored
Merge pull request #29451 from amcasey/GH27937
Consider JSX namespace imports when moving statements between files
2 parents a9c5a04 + 4029e70 commit 94ca501

File tree

5 files changed

+122
-1
lines changed

5 files changed

+122
-1
lines changed

src/services/refactors/moveToNewFile.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,12 @@ namespace ts.refactor {
460460
const oldImportsNeededByNewFile = new SymbolSet();
461461
const newFileImportsFromOldFile = new SymbolSet();
462462

463+
const containsJsx = find(toMove, statement => !!(statement.transformFlags & TransformFlags.ContainsJsx));
464+
const jsxNamespaceSymbol = getJsxNamespaceSymbol(containsJsx);
465+
if (jsxNamespaceSymbol) { // Might not exist (e.g. in non-compiling code)
466+
oldImportsNeededByNewFile.add(jsxNamespaceSymbol);
467+
}
468+
463469
for (const statement of toMove) {
464470
forEachTopLevelDeclaration(statement, decl => {
465471
movedSymbols.add(Debug.assertDefined(isExpressionStatement(decl) ? checker.getSymbolAtLocation(decl.expression.left) : decl.symbol));
@@ -485,13 +491,35 @@ namespace ts.refactor {
485491
for (const statement of oldFile.statements) {
486492
if (contains(toMove, statement)) continue;
487493

494+
// jsxNamespaceSymbol will only be set iff it is in oldImportsNeededByNewFile.
495+
if (jsxNamespaceSymbol && !!(statement.transformFlags & TransformFlags.ContainsJsx)) {
496+
unusedImportsFromOldFile.delete(jsxNamespaceSymbol);
497+
}
498+
488499
forEachReference(statement, checker, symbol => {
489500
if (movedSymbols.has(symbol)) oldFileImportsFromNewFile.add(symbol);
490501
unusedImportsFromOldFile.delete(symbol);
491502
});
492503
}
493504

494505
return { movedSymbols, newFileImportsFromOldFile, oldFileImportsFromNewFile, oldImportsNeededByNewFile, unusedImportsFromOldFile };
506+
507+
function getJsxNamespaceSymbol(containsJsx: Node | undefined) {
508+
if (containsJsx === undefined) {
509+
return undefined;
510+
}
511+
512+
const jsxNamespace = checker.getJsxNamespace(containsJsx);
513+
514+
// Strictly speaking, this could resolve to a symbol other than the JSX namespace.
515+
// This will produce erroneous output (probably, an incorrectly copied import) but
516+
// is expected to be very rare and easily reversible.
517+
const jsxNamespaceSymbol = checker.resolveName(jsxNamespace, containsJsx, SymbolFlags.Namespace, /*excludeGlobals*/ true);
518+
519+
return !!jsxNamespaceSymbol && some(jsxNamespaceSymbol.declarations, isInImport)
520+
? jsxNamespaceSymbol
521+
: undefined;
522+
}
495523
}
496524

497525
// Below should all be utilities
@@ -512,7 +540,7 @@ namespace ts.refactor {
512540
}
513541
function isVariableDeclarationInImport(decl: VariableDeclaration) {
514542
return isSourceFile(decl.parent.parent.parent) &&
515-
decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true);
543+
!!decl.initializer && isRequireCall(decl.initializer, /*checkArgumentIsStringLiteralLike*/ true);
516544
}
517545

518546
function filterImport(i: SupportedImport, moduleSpecifier: StringLiteralLike, keep: (name: Identifier) => boolean): SupportedImportStatement | undefined {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @jsx: preserve
4+
// @noLib: true
5+
// @libFiles: react.d.ts,lib.d.ts
6+
7+
// @Filename: file.tsx
8+
//// import React = require('react');
9+
//// [|<div/>;|]
10+
//// 1;
11+
12+
verify.moveToNewFile({
13+
newFileContents: {
14+
"/tests/cases/fourslash/file.tsx":
15+
`1;`,
16+
"/tests/cases/fourslash/newFile.tsx":
17+
`import React = require('react');
18+
<div />;
19+
`,
20+
}
21+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @jsx: preserve
4+
// @noLib: true
5+
// @libFiles: react.d.ts,lib.d.ts
6+
7+
// @Filename: file.tsx
8+
//// import React = require('react');
9+
//// [|<div/>;|]
10+
//// <div/>;
11+
12+
verify.moveToNewFile({
13+
newFileContents: {
14+
"/tests/cases/fourslash/file.tsx":
15+
`import React = require('react');
16+
<div/>;`,
17+
"/tests/cases/fourslash/newFile.tsx":
18+
`import React = require('react');
19+
<div />;
20+
`,
21+
}
22+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @jsx: preserve
4+
// @noLib: true
5+
// @libFiles: react.d.ts,lib.d.ts
6+
7+
// @Filename: file.tsx
8+
//// import React = require('react');
9+
//// [|1;|]
10+
//// <div/>;
11+
12+
verify.moveToNewFile({
13+
newFileContents: {
14+
"/tests/cases/fourslash/file.tsx":
15+
`import React = require('react');
16+
<div/>;`,
17+
"/tests/cases/fourslash/newFile.tsx":
18+
`1;
19+
`,
20+
}
21+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @jsx: preserve
4+
// @noLib: true
5+
// @libFiles: react.d.ts,lib.d.ts,leftpad.d.ts
6+
7+
// @Filename: file.tsx
8+
//// import React = require('leftpad');
9+
//// [|function F() {
10+
//// const React = import("react");
11+
//// <div/>;
12+
//// }|]
13+
//// React;
14+
15+
verify.moveToNewFile({
16+
newFileContents: {
17+
"/tests/cases/fourslash/file.tsx":
18+
`import React = require('leftpad');
19+
React;`,
20+
// NB: A perfect implementation would not copy over the import
21+
"/tests/cases/fourslash/F.tsx":
22+
`import React = require('leftpad');
23+
function F() {
24+
const React = import("react");
25+
<div />;
26+
}
27+
`,
28+
}
29+
});

0 commit comments

Comments
 (0)