Skip to content

Commit

Permalink
Make ts-types transformer work with TS >= 4.8 (#8661)
Browse files Browse the repository at this point in the history
  • Loading branch information
mischnic authored Nov 27, 2022
1 parent 143b773 commit c0f5351
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 74 deletions.
9 changes: 6 additions & 3 deletions packages/transformers/typescript-types/src/collect.js
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit c0f5351

Please sign in to comment.