Skip to content

Commit

Permalink
Transforming variable declarations with binding patterns into a list …
Browse files Browse the repository at this point in the history
…of separated variable declarations

Fixes #315
  • Loading branch information
timocov committed Apr 16, 2024
1 parent d3c2225 commit f25bc0f
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 1 deletion.
75 changes: 74 additions & 1 deletion src/compile-dts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,66 @@ export function compileDts(rootFiles: readonly string[], preferredConfigPath?: s
return ts.createSourceFile(fileName, storedValue, languageVersion);
}

return originalGetSourceFile(fileName, languageVersion, onError);
const sourceFile = originalGetSourceFile(fileName, languageVersion, onError);
if (sourceFile === undefined) {
return sourceFile;
}

// this means that a source file contains "weird" variable statement like the following:
// `declare const { FOO = 123 }` or `declare const [BAR = 321]`
// as you can see there is no type nor initializer for a binding pattern itself but rather each variable separately
// it seems that it is not possible to get such output from the typescript file but only manually write it in a declaration file
// and such syntax breaks the tool, so we need to "tweak" it a bit
// which practically means "to transform a binding pattern to many variable declarations"
if (nodeContainsVariableWithBindingPatternsWithoutInitializer(sourceFile)) {
// and it looks like the only option to do this is to print a file with transformation and then parse it back
// it is not quite performant, but apparently there is no other easy way to do it ¯\_(ツ)_/¯
const printer = ts.createPrinter(
undefined,
{
substituteNode: (hint: ts.EmitHint, node: ts.Node) => {
if (!ts.isVariableStatement(node)) {
return node;
}

if (!isVariableWithBindingPatternsWithoutInitializer(node)) {
return node;
}

const newDeclarations: ts.VariableDeclaration[] = [];

for (const decl of node.declarationList.declarations) {
if ((ts.isObjectBindingPattern(decl.name) || ts.isArrayBindingPattern(decl.name)) && decl.initializer === undefined) {
for (const element of decl.name.elements) {
if (ts.isOmittedExpression(element)) {
continue;
}

// each binding element should create a separate variable declaration
// so `declare const { FOO = 123, BAR = 321 }` should become `declare const FOO = 123, BAR = 321`
// (or the same for array binding pattern)
newDeclarations.push(ts.factory.createVariableDeclaration(element.name, undefined, undefined, element.initializer));
}
} else {
newDeclarations.push(decl);
}
}

return ts.factory.createVariableStatement(
node.modifiers,
ts.factory.createVariableDeclarationList(newDeclarations, node.declarationList.flags)
);
},
}
);

// I wish there was a better way of modifying AST without printing and parsing it again
// e.g. modify a node directly in-place like it is possible in transformers
const updatedSourceFileText = printer.printNode(ts.EmitHint.Unspecified, sourceFile, sourceFile.getSourceFile());
return ts.createSourceFile(fileName, updatedSourceFileText, sourceFile.languageVersion, true);
}

return sourceFile;
};

const rootFilesRemapping = new Map<string, string>();
Expand All @@ -107,6 +166,20 @@ export function compileDts(rootFiles: readonly string[], preferredConfigPath?: s
return { program, rootFilesRemapping };
}

function isVariableWithBindingPatternsWithoutInitializer(node: ts.VariableStatement): boolean {
return node.declarationList.declarations.some((decl: ts.VariableDeclaration) => {
return (ts.isObjectBindingPattern(decl.name) || ts.isArrayBindingPattern(decl.name)) && decl.initializer === undefined;
});
}

function nodeContainsVariableWithBindingPatternsWithoutInitializer(node: ts.Node): boolean | undefined {
if (ts.isVariableStatement(node)) {
return isVariableWithBindingPatternsWithoutInitializer(node);
}

return ts.forEachChild(node, nodeContainsVariableWithBindingPatternsWithoutInitializer);
}

function createCachingCompilerHost(compilerOptions: ts.CompilerOptions): ts.CompilerHost {
const host = ts.createIncrementalCompilerHost(compilerOptions);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { TestCaseConfig } from '../test-case-config';

const config: TestCaseConfig = {};

export = config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export declare const {
FOO = 123,
BAZ = 42,
};

export const [
BAR = '321',
FOOBAR = 1337,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('../run-test-case').runTestCase(__dirname);
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { BAR, FOO } from './consts';

export type BarType = typeof BAR;
export type FooType = typeof FOO;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
declare const FOO = 123, BAZ = 42;
declare const BAR = "321", FOOBAR = 1337;
export type BarType = typeof BAR;
export type FooType = typeof FOO;

export {};

0 comments on commit f25bc0f

Please sign in to comment.