Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ts-types transformer work with TS >= 4.8 #8661

Merged
merged 7 commits into from Nov 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 6 additions & 3 deletions packages/transformers/typescript-types/src/collect.js
Expand Up @@ -11,13 +11,16 @@ export function collect(
context: any,
sourceFile: any,
): any {
// Factory only exists on TS >= 4.0
const {factory = ts} = context;

// When module definitions are nested inside each other (e.g with module augmentation),
// we want to keep track of the hierarchy so we can associated nodes with the right module.
const moduleStack: Array<?TSModule> = [];
let _currentModule: ?TSModule;
let visit = (node: any): any => {
if (ts.isBundle(node)) {
return ts.updateBundle(node, ts.visitNodes(node.sourceFiles, visit));
return factory.updateBundle(node, ts.visitNodes(node.sourceFiles, visit));
}

if (ts.isModuleDeclaration(node)) {
Expand Down Expand Up @@ -85,12 +88,12 @@ export function collect(
currentModule.addExport('default', node.expression.text);
}

if (isDeclaration(ts, node)) {
if (isDeclaration(node)) {
if (node.name) {
currentModule.addLocal(node.name.text, node);
}

let name = getExportedName(ts, node);
let name = getExportedName(node);
if (name) {
currentModule.addLocal(name, node);
currentModule.addExport(name, name);
Expand Down
99 changes: 61 additions & 38 deletions packages/transformers/typescript-types/src/shake.js
Expand Up @@ -4,13 +4,22 @@ import type {TSModuleGraph} from './TSModuleGraph';

import ts from 'typescript';
import nullthrows from 'nullthrows';
import {getExportedName, isDeclaration, createImportSpecifier} from './utils';
import {getExportedName, isDeclaration} from './utils';
import {
createImportClause,
createImportDeclaration,
createImportSpecifier,
updateExportDeclaration,
} from './wrappers';

export function shake(
moduleGraph: TSModuleGraph,
context: any,
sourceFile: any,
): any {
// Factory only exists on TS >= 4.0
const {factory = ts} = context;

// We traverse things out of order which messes with typescript's internal state.
// We don't rely on the lexical environment, so just overwrite with noops to avoid errors.
context.suspendLexicalEnvironment = () => {};
Expand All @@ -28,7 +37,7 @@ export function shake(
let _currentModule: ?TSModule;
let visit = (node: any): any => {
if (ts.isBundle(node)) {
return ts.updateBundle(node, ts.visitNodes(node.sourceFiles, visit));
return factory.updateBundle(node, ts.visitNodes(node.sourceFiles, visit));
}

// Flatten all module declarations into the top-level scope
Expand All @@ -43,7 +52,7 @@ export function shake(
node.modifiers.splice(
index,
0,
ts.createModifier(ts.SyntaxKind.DeclareKeyword),
factory.createModifier(ts.SyntaxKind.DeclareKeyword),
);
return node;
}
Expand All @@ -55,7 +64,7 @@ export function shake(
_currentModule = moduleStack.pop();

if (isFirstModule && !addedGeneratedImports) {
statements.unshift(...generateImports(moduleGraph));
statements.unshift(...generateImports(factory, moduleGraph));
addedGeneratedImports = true;
}

Expand Down Expand Up @@ -92,12 +101,14 @@ export function shake(
}

if (exported.length > 0) {
return ts.updateExportDeclaration(
return updateExportDeclaration(
factory,
node,
undefined, // decorators
undefined, // modifiers
ts.updateNamedExports(node.exportClause, exported),
false, // isTypeOnly
factory.updateNamedExports(node.exportClause, exported),
undefined, // moduleSpecifier
undefined, // assertClause
);
}
}
Expand All @@ -114,16 +125,15 @@ export function shake(
}
}

if (isDeclaration(ts, node)) {
let name = getExportedName(ts, node) || node.name.text;
if (isDeclaration(node)) {
let name = getExportedName(node) || node.name.text;

// Remove unused declarations
if (!currentModule.used.has(name)) {
return null;
}

// Remove original export modifiers
node = ts.getMutableClone(node);
node.modifiers = (node.modifiers || []).filter(
m =>
m.kind !== ts.SyntaxKind.ExportKeyword &&
Expand All @@ -133,23 +143,27 @@ export function shake(
// Rename declarations
let newName = currentModule.getName(name);
if (newName !== name && newName !== 'default') {
node.name = ts.createIdentifier(newName);
node.name = factory.createIdentifier(newName);
}

// Export declarations that should be exported
if (exportedNames.get(newName) === currentModule) {
if (newName === 'default') {
node.modifiers.unshift(
ts.createModifier(ts.SyntaxKind.DefaultKeyword),
factory.createModifier(ts.SyntaxKind.DefaultKeyword),
);
}

node.modifiers.unshift(ts.createModifier(ts.SyntaxKind.ExportKeyword));
node.modifiers.unshift(
factory.createModifier(ts.SyntaxKind.ExportKeyword),
);
} else if (
ts.isFunctionDeclaration(node) ||
ts.isClassDeclaration(node)
) {
node.modifiers.unshift(ts.createModifier(ts.SyntaxKind.DeclareKeyword));
node.modifiers.unshift(
factory.createModifier(ts.SyntaxKind.DeclareKeyword),
);
}
}

Expand All @@ -173,10 +187,14 @@ export function shake(
d => exportedNames.get(d.name.text) === currentModule,
);
if (isExported) {
node.modifiers.unshift(ts.createModifier(ts.SyntaxKind.ExportKeyword));
node.modifiers.unshift(
factory.createModifier(ts.SyntaxKind.ExportKeyword),
);
} else {
// Otherwise, add `declare` modifier (required for top-level declarations in d.ts files).
node.modifiers.unshift(ts.createModifier(ts.SyntaxKind.DeclareKeyword));
node.modifiers.unshift(
factory.createModifier(ts.SyntaxKind.DeclareKeyword),
);
}

return node;
Expand All @@ -193,7 +211,7 @@ export function shake(
if (ts.isIdentifier(node) && currentModule.names.has(node.text)) {
let newName = nullthrows(currentModule.getName(node.text));
if (newName !== 'default') {
return ts.createIdentifier(newName);
return factory.createIdentifier(newName);
}
}

Expand All @@ -205,11 +223,11 @@ export function shake(
node.right.text,
);
if (resolved && resolved.module.hasBinding(resolved.name)) {
return ts.createIdentifier(resolved.name);
return factory.createIdentifier(resolved.name);
} else {
return ts.updateQualifiedName(
return factory.updateQualifiedName(
node,
ts.createIdentifier(currentModule.getName(node.left.text)),
factory.createIdentifier(currentModule.getName(node.left.text)),
node.right,
);
}
Expand All @@ -231,61 +249,66 @@ export function shake(
return ts.visitNode(sourceFile, visit);
}

function generateImports(moduleGraph: TSModuleGraph) {
function generateImports(factory: any, moduleGraph: TSModuleGraph) {
let importStatements = [];
for (let [specifier, names] of moduleGraph.getAllImports()) {
let defaultSpecifier;
let namespaceSpecifier;
let namedSpecifiers = [];
for (let [name, imported] of names) {
if (imported === 'default') {
defaultSpecifier = ts.createIdentifier(name);
defaultSpecifier = factory.createIdentifier(name);
} else if (imported === '*') {
namespaceSpecifier = ts.createNamespaceImport(
ts.createIdentifier(name),
namespaceSpecifier = factory.createNamespaceImport(
factory.createIdentifier(name),
);
} else {
namedSpecifiers.push(
createImportSpecifier(
ts,
name === imported ? undefined : ts.createIdentifier(imported),
ts.createIdentifier(name),
factory,
false,
name === imported ? undefined : factory.createIdentifier(imported),
factory.createIdentifier(name),
),
);
}
}

if (namespaceSpecifier) {
let importClause = ts.createImportClause(
let importClause = createImportClause(
factory,
false,
defaultSpecifier,
namespaceSpecifier,
);
importStatements.push(
ts.createImportDeclaration(
undefined,
createImportDeclaration(
factory,
undefined,
importClause,
// $FlowFixMe
ts.createLiteral(specifier),
factory.createStringLiteral(specifier),
undefined,
),
);
defaultSpecifier = undefined;
}

if (defaultSpecifier || namedSpecifiers.length > 0) {
let importClause = ts.createImportClause(
let importClause = createImportClause(
factory,
false,
defaultSpecifier,
namedSpecifiers.length > 0
? ts.createNamedImports(namedSpecifiers)
? factory.createNamedImports(namedSpecifiers)
: undefined,
);
importStatements.push(
ts.createImportDeclaration(
undefined,
createImportDeclaration(
factory,
undefined,
importClause,
// $FlowFixMe
ts.createLiteral(specifier),
factory.createStringLiteral(specifier),
undefined,
),
);
}
Expand Down
26 changes: 3 additions & 23 deletions packages/transformers/typescript-types/src/utils.js
@@ -1,8 +1,7 @@
// @flow
import typeof TypeScriptModule from 'typescript'; // eslint-disable-line import/no-extraneous-dependencies
import type {Identifier, ImportSpecifier} from 'typescript';
import ts from 'typescript';

export function getExportedName(ts: TypeScriptModule, node: any): ?string {
export function getExportedName(node: any): ?string {
if (!node.modifiers) {
return null;
}
Expand All @@ -18,7 +17,7 @@ export function getExportedName(ts: TypeScriptModule, node: any): ?string {
return node.name.text;
}

export function isDeclaration(ts: TypeScriptModule, node: any): boolean {
export function isDeclaration(node: any): boolean {
return (
ts.isFunctionDeclaration(node) ||
ts.isClassDeclaration(node) ||
Expand All @@ -27,22 +26,3 @@ export function isDeclaration(ts: TypeScriptModule, node: any): boolean {
ts.isTypeAliasDeclaration(node)
);
}

export function createImportSpecifier(
ts: TypeScriptModule,
propertyName: Identifier | void,
name: Identifier,
isTypeOnly: boolean = false,
): ImportSpecifier {
const [majorVersion, minorVersion] = ts.versionMajorMinor
.split('.')
.map(num => parseInt(num, 10));
// The signature of createImportSpecifier had a breaking change in Typescript 4.5.
// see: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html#type-modifiers-on-import-names
if (majorVersion > 4 || (majorVersion === 4 && minorVersion >= 5)) {
// $FlowFixMe
return ts.createImportSpecifier(isTypeOnly, propertyName, name);
} else {
return ts.createImportSpecifier(propertyName, name);
}
}