@eliasku/ts-transformers 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -7
- package/package.json +1 -1
- package/src/const-enum/registry.ts +1 -1
- package/src/index.ts +32 -54
- package/src/typescript-helpers.ts +0 -204
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ class A{publicApi(){},a(){},b=1}
|
|
|
52
52
|
|
|
53
53
|
### Const Enum Inlining
|
|
54
54
|
|
|
55
|
-
Replaces const enum accesses with literal values and
|
|
55
|
+
Replaces const enum accesses with literal values and transforms declarations to remove codegen, but keep symbol to preserve default import/export flow.
|
|
56
56
|
|
|
57
57
|
```typescript
|
|
58
58
|
// Before
|
|
@@ -60,10 +60,14 @@ const enum Status {
|
|
|
60
60
|
Active = 1,
|
|
61
61
|
Inactive = 0,
|
|
62
62
|
}
|
|
63
|
-
const status = Status.Active;
|
|
63
|
+
export const status = Status.Active;
|
|
64
64
|
|
|
65
|
-
// After transformer
|
|
66
|
-
|
|
65
|
+
// After transformer
|
|
66
|
+
export let Status: {};
|
|
67
|
+
export const status = 1;
|
|
68
|
+
|
|
69
|
+
// After minifier
|
|
70
|
+
export const status=1;
|
|
67
71
|
```
|
|
68
72
|
|
|
69
73
|
## Usage
|
|
@@ -79,11 +83,16 @@ const bundle = await rollup({
|
|
|
79
83
|
input: "./src/index.ts",
|
|
80
84
|
plugins: [
|
|
81
85
|
typescript({
|
|
86
|
+
compilerOptions: {
|
|
87
|
+
target: "ESNext",
|
|
88
|
+
module: "ESNext",
|
|
89
|
+
moduleResolution: "Bundler",
|
|
90
|
+
lib: ["DOM", "ESNext"],
|
|
91
|
+
},
|
|
82
92
|
transformers: (program) => ({
|
|
83
93
|
before: [
|
|
84
94
|
optimizer(program, {
|
|
85
|
-
entrySourceFiles: ["
|
|
86
|
-
inlineConstEnums: true,
|
|
95
|
+
entrySourceFiles: ["src/index.ts"],
|
|
87
96
|
}),
|
|
88
97
|
],
|
|
89
98
|
}),
|
|
@@ -107,6 +116,8 @@ await build({
|
|
|
107
116
|
});
|
|
108
117
|
```
|
|
109
118
|
|
|
119
|
+
**Note**: This plugin uses `@rollup/plugin-typescript`. The transformer receives a TypeScript `Program` object directly.
|
|
120
|
+
|
|
110
121
|
## Options
|
|
111
122
|
|
|
112
123
|
### entrySourceFiles (required)
|
|
@@ -156,7 +167,7 @@ class AppComponent {
|
|
|
156
167
|
|
|
157
168
|
### inlineConstEnums (optional, default: true)
|
|
158
169
|
|
|
159
|
-
Inline const enum values and
|
|
170
|
+
Inline const enum values and transform declarations to preserve export/import flow.
|
|
160
171
|
|
|
161
172
|
## Complete Example
|
|
162
173
|
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@ import ts from "typescript";
|
|
|
2
2
|
import { EnumValue, EvaluationContext } from "./evaluator";
|
|
3
3
|
import { EnumEvaluator } from "./evaluator";
|
|
4
4
|
import { isConstEnumSymbol } from "./utils";
|
|
5
|
-
import { hasModifier } from "../
|
|
5
|
+
import { hasModifier } from "../utils/symbol-utils";
|
|
6
6
|
|
|
7
7
|
export interface ConstEnumInfo {
|
|
8
8
|
declaration: ts.EnumDeclaration;
|
package/src/index.ts
CHANGED
|
@@ -48,27 +48,9 @@ function createTransformerFactory(
|
|
|
48
48
|
if (fullOptions.inlineConstEnums !== false) {
|
|
49
49
|
if (ts.isPropertyAccessExpression(node)) {
|
|
50
50
|
const inlined = tryInlineConstEnum(node);
|
|
51
|
-
if (inlined)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (ts.isImportSpecifier(node)) {
|
|
55
|
-
const removed = tryRemoveConstEnumImport(node);
|
|
56
|
-
if (removed === undefined || node.isTypeOnly) {
|
|
57
|
-
return undefined;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (ts.isExportSpecifier(node)) {
|
|
62
|
-
const removed = tryRemoveConstEnumExport(node);
|
|
63
|
-
if (removed === undefined || node.isTypeOnly) {
|
|
64
|
-
return undefined;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (ts.isImportClause(node)) {
|
|
69
|
-
const removed = tryRemoveConstEnumImportClause(node);
|
|
70
|
-
if (removed === undefined) {
|
|
71
|
-
return undefined;
|
|
51
|
+
if (inlined) {
|
|
52
|
+
ts.setOriginalNode(inlined, node);
|
|
53
|
+
return inlined;
|
|
72
54
|
}
|
|
73
55
|
}
|
|
74
56
|
}
|
|
@@ -575,7 +557,6 @@ function createTransformerFactory(
|
|
|
575
557
|
return putToCache(nodeSymbol, VisibilityType.External);
|
|
576
558
|
}
|
|
577
559
|
|
|
578
|
-
// TODO: mute enum renames
|
|
579
560
|
if (ts.isEnumDeclaration(currentNode)) {
|
|
580
561
|
return putToCache(nodeSymbol, VisibilityType.External);
|
|
581
562
|
}
|
|
@@ -667,39 +648,22 @@ function createTransformerFactory(
|
|
|
667
648
|
|
|
668
649
|
const memberValue = enumInfo.members.get(node.name.text)?.value;
|
|
669
650
|
if (memberValue === undefined || memberValue === null) return null;
|
|
651
|
+
const newNode = enumEvaluator.createLiteral(memberValue);
|
|
670
652
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
const importedType = typeChecker.getTypeAtLocation(node);
|
|
676
|
-
if (isConstEnumType(importedType)) {
|
|
677
|
-
return undefined;
|
|
678
|
-
}
|
|
679
|
-
return node;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
function tryRemoveConstEnumExport(node: ts.ExportSpecifier): ts.ExportSpecifier | undefined {
|
|
683
|
-
const importedType = typeChecker.getTypeAtLocation(node);
|
|
684
|
-
if (isConstEnumType(importedType)) {
|
|
685
|
-
return undefined;
|
|
686
|
-
}
|
|
687
|
-
return node;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
function tryRemoveConstEnumImportClause(node: ts.ImportClause): ts.ImportClause | undefined {
|
|
691
|
-
if (!node.name) return node;
|
|
692
|
-
const type = typeChecker.getTypeAtLocation(node.name);
|
|
693
|
-
if (isConstEnumType(type)) {
|
|
694
|
-
return undefined;
|
|
695
|
-
}
|
|
696
|
-
return node;
|
|
653
|
+
const r = ts.setOriginalNode(newNode, node);
|
|
654
|
+
ts.setSourceMapRange(newNode, ts.getSourceMapRange(node));
|
|
655
|
+
(newNode as any).symbol = (node as any).symbol; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
656
|
+
return r;
|
|
697
657
|
}
|
|
698
658
|
|
|
699
659
|
return (sourceFile: ts.SourceFile) => {
|
|
700
660
|
function handleEnumDeclaration(node: ts.EnumDeclaration): ts.EnumDeclaration | undefined {
|
|
701
|
-
if (fullOptions.inlineConstEnums === false)
|
|
702
|
-
|
|
661
|
+
if (fullOptions.inlineConstEnums === false) {
|
|
662
|
+
return node;
|
|
663
|
+
}
|
|
664
|
+
if (!hasModifier(node, ts.SyntaxKind.ConstKeyword)) {
|
|
665
|
+
return node;
|
|
666
|
+
}
|
|
703
667
|
|
|
704
668
|
if (sourceFile.isDeclarationFile) {
|
|
705
669
|
return ts.factory.updateEnumDeclaration(
|
|
@@ -709,16 +673,30 @@ function createTransformerFactory(
|
|
|
709
673
|
node.members,
|
|
710
674
|
);
|
|
711
675
|
}
|
|
712
|
-
|
|
676
|
+
|
|
677
|
+
return ts.factory.createVariableStatement(
|
|
678
|
+
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
679
|
+
ts.factory.createVariableDeclarationList(
|
|
680
|
+
[
|
|
681
|
+
ts.factory.createVariableDeclaration(
|
|
682
|
+
node.name, // Reuse enum name
|
|
683
|
+
undefined,
|
|
684
|
+
ts.factory.createTypeLiteralNode([]), // Empty object type: {}
|
|
685
|
+
undefined, // No initializer (empty declaration)
|
|
686
|
+
),
|
|
687
|
+
],
|
|
688
|
+
ts.NodeFlags.Let, // Sets the 'const' modifier
|
|
689
|
+
),
|
|
690
|
+
) as unknown as ts.EnumDeclaration;
|
|
713
691
|
}
|
|
714
692
|
|
|
715
693
|
function wrapTransformNode(node: ts.Node): ts.Node | undefined {
|
|
716
694
|
if (ts.isEnumDeclaration(node)) {
|
|
717
695
|
const result = handleEnumDeclaration(node);
|
|
718
|
-
if (result === undefined) {
|
|
719
|
-
return undefined;
|
|
720
|
-
}
|
|
721
696
|
if (result !== node) {
|
|
697
|
+
if (result) {
|
|
698
|
+
return transformNode(result);
|
|
699
|
+
}
|
|
722
700
|
return result;
|
|
723
701
|
}
|
|
724
702
|
}
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
|
|
3
|
-
const namedDeclarationKinds = [
|
|
4
|
-
ts.SyntaxKind.InterfaceDeclaration,
|
|
5
|
-
ts.SyntaxKind.ClassDeclaration,
|
|
6
|
-
ts.SyntaxKind.EnumDeclaration,
|
|
7
|
-
ts.SyntaxKind.TypeAliasDeclaration,
|
|
8
|
-
ts.SyntaxKind.ModuleDeclaration,
|
|
9
|
-
ts.SyntaxKind.FunctionDeclaration,
|
|
10
|
-
ts.SyntaxKind.VariableDeclaration,
|
|
11
|
-
ts.SyntaxKind.PropertySignature,
|
|
12
|
-
ts.SyntaxKind.Parameter,
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
export const isNodeNamedDeclaration = (node: ts.Node): node is ts.NamedDeclaration =>
|
|
16
|
-
namedDeclarationKinds.indexOf(node.kind) !== -1;
|
|
17
|
-
|
|
18
|
-
export function getActualSymbol(symbol: ts.Symbol, typeChecker: ts.TypeChecker): ts.Symbol {
|
|
19
|
-
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
20
|
-
symbol = typeChecker.getAliasedSymbol(symbol);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return symbol;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function splitTransientSymbol(symbol: ts.Symbol, typeChecker: ts.TypeChecker): ts.Symbol[] {
|
|
27
|
-
// actually I think we even don't need to operate/use "Transient" symbols anywhere
|
|
28
|
-
// it's kind of aliased symbol, but just merged
|
|
29
|
-
// but it's hard to refractor everything to use array of symbols instead of just symbol
|
|
30
|
-
// so let's fix it for some places
|
|
31
|
-
if ((symbol.flags & ts.SymbolFlags.Transient) === 0) {
|
|
32
|
-
return [symbol];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// "Transient" symbol is kinda "merged" symbol
|
|
36
|
-
// I don't really know is this way to "split" is correct
|
|
37
|
-
// but it seems that it works for now ¯\_(ツ)_/¯
|
|
38
|
-
const declarations = getDeclarationsForSymbol(symbol);
|
|
39
|
-
const result: ts.Symbol[] = [];
|
|
40
|
-
for (const declaration of declarations) {
|
|
41
|
-
if (!isNodeNamedDeclaration(declaration) || declaration.name === undefined) {
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const sym = typeChecker.getSymbolAtLocation(declaration.name);
|
|
46
|
-
if (sym === undefined) {
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
result.push(getActualSymbol(sym, typeChecker));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return result;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function getDeclarationsForSymbol(symbol: ts.Symbol): ts.Declaration[] {
|
|
57
|
-
const result: ts.Declaration[] = [];
|
|
58
|
-
|
|
59
|
-
if (symbol.declarations !== undefined) {
|
|
60
|
-
result.push(...symbol.declarations);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (symbol.valueDeclaration !== undefined) {
|
|
64
|
-
// push valueDeclaration might be already in declarations array
|
|
65
|
-
// so let's check first to avoid duplication nodes
|
|
66
|
-
if (!result.includes(symbol.valueDeclaration)) {
|
|
67
|
-
result.push(symbol.valueDeclaration);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return result;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function getExportsForSourceFile(typeChecker: ts.TypeChecker, sourceFileSymbol: ts.Symbol): ts.Symbol[] {
|
|
75
|
-
if (sourceFileSymbol.exports !== undefined) {
|
|
76
|
-
const commonJsExport = sourceFileSymbol.exports.get(ts.InternalSymbolName.ExportEquals);
|
|
77
|
-
if (commonJsExport !== undefined) {
|
|
78
|
-
return [getActualSymbol(commonJsExport, typeChecker)];
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const result: ts.Symbol[] = typeChecker.getExportsOfModule(sourceFileSymbol);
|
|
83
|
-
|
|
84
|
-
if (sourceFileSymbol.exports !== undefined) {
|
|
85
|
-
const defaultExportSymbol = sourceFileSymbol.exports.get(ts.InternalSymbolName.Default);
|
|
86
|
-
if (defaultExportSymbol !== undefined) {
|
|
87
|
-
if (!result.includes(defaultExportSymbol)) {
|
|
88
|
-
// it seems that default export is always returned by getExportsOfModule
|
|
89
|
-
// but let's add it to be sure add if there is no such export
|
|
90
|
-
result.push(defaultExportSymbol);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return result.map((symbol: ts.Symbol) => getActualSymbol(symbol, typeChecker));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export type ClassMember =
|
|
99
|
-
| ts.MethodDeclaration
|
|
100
|
-
| ts.PropertyDeclaration
|
|
101
|
-
| ts.GetAccessorDeclaration
|
|
102
|
-
| ts.SetAccessorDeclaration;
|
|
103
|
-
|
|
104
|
-
export const isClassMember = (node: ts.Node): node is ClassMember =>
|
|
105
|
-
ts.isMethodDeclaration(node) || ts.isPropertyDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node);
|
|
106
|
-
|
|
107
|
-
export function getClassOfMemberSymbol(
|
|
108
|
-
nodeSymbol: ts.Symbol,
|
|
109
|
-
): ts.ClassLikeDeclaration | ts.ObjectLiteralExpression | ts.TypeLiteralNode | ts.InterfaceDeclaration | null {
|
|
110
|
-
const classMembers = getClassMemberDeclarations(nodeSymbol);
|
|
111
|
-
if (classMembers.length !== 0) {
|
|
112
|
-
// we need any member to get class' declaration
|
|
113
|
-
const classMember = classMembers[0];
|
|
114
|
-
if (isConstructorParameter(classMember)) {
|
|
115
|
-
return (classMember.parent as ts.ConstructorDeclaration).parent;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// we're sure that it is a class, not interface
|
|
119
|
-
return classMember.parent as ts.ClassLikeDeclaration | ts.ObjectLiteralExpression;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export const hasPrivateKeyword = (node: ClassMember | ts.ParameterDeclaration) =>
|
|
126
|
-
hasModifier(node, ts.SyntaxKind.PrivateKeyword);
|
|
127
|
-
|
|
128
|
-
function getModifiers(node: ts.Node): readonly ts.Modifier[] {
|
|
129
|
-
if (isBreakingTypeScriptApi(ts)) {
|
|
130
|
-
if (!ts.canHaveModifiers(node)) {
|
|
131
|
-
return [];
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return ts.getModifiers(node) || [];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
138
|
-
// @ts-ignore
|
|
139
|
-
return node.modifiers || [];
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export const hasModifier = (node: ts.Node, modifier: ts.SyntaxKind): boolean =>
|
|
143
|
-
ts.canHaveModifiers(node) && getModifiers(node)?.some((mod) => mod.kind === modifier);
|
|
144
|
-
|
|
145
|
-
function getDecorators(node: ts.Node): readonly unknown[] {
|
|
146
|
-
if (isBreakingTypeScriptApi(ts)) {
|
|
147
|
-
if (!ts.canHaveDecorators(node)) {
|
|
148
|
-
return [];
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return ts.getDecorators(node) || [];
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
155
|
-
// @ts-ignore
|
|
156
|
-
return node.decorators || [];
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export const hasDecorators = (node: ts.Node) => getDecorators(node).length !== 0;
|
|
160
|
-
|
|
161
|
-
export const isConstructorParameter = (node: ts.Node): node is ts.ParameterDeclaration =>
|
|
162
|
-
ts.isParameter(node) &&
|
|
163
|
-
ts.isConstructorDeclaration(node.parent as ts.Node) &&
|
|
164
|
-
(hasModifier(node, ts.SyntaxKind.PublicKeyword) ||
|
|
165
|
-
hasModifier(node, ts.SyntaxKind.ProtectedKeyword) ||
|
|
166
|
-
hasModifier(node, ts.SyntaxKind.PrivateKeyword) ||
|
|
167
|
-
hasModifier(node, ts.SyntaxKind.ReadonlyKeyword));
|
|
168
|
-
|
|
169
|
-
function getClassMemberDeclarations(symbol: ts.Symbol | undefined): (ClassMember | ts.ParameterDeclaration)[] {
|
|
170
|
-
if (symbol === undefined) {
|
|
171
|
-
return [];
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const declarations = symbol.getDeclarations();
|
|
175
|
-
if (declarations === undefined) {
|
|
176
|
-
return [];
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return declarations.filter((x: ts.Declaration): x is ClassMember | ts.ParameterDeclaration => {
|
|
180
|
-
return isClassMember(x) || isConstructorParameter(x);
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export const isSymbolClassMember = (symbol: ts.Symbol | undefined) => getClassMemberDeclarations(symbol).length !== 0;
|
|
185
|
-
|
|
186
|
-
export const isPrivateClassMember = (symbol: ts.Symbol | undefined) =>
|
|
187
|
-
getClassMemberDeclarations(symbol).some(hasPrivateKeyword);
|
|
188
|
-
|
|
189
|
-
export function getNodeJSDocComment(node: ts.Node): string {
|
|
190
|
-
const start = node.getStart();
|
|
191
|
-
const jsDocStart = node.getStart(undefined, true);
|
|
192
|
-
return node.getSourceFile().getFullText().substring(jsDocStart, start).trim();
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// decorators and modifiers-related api added in ts 4.8
|
|
196
|
-
interface BreakingTypeScriptApi {
|
|
197
|
-
canHaveDecorators(node: ts.Node): boolean;
|
|
198
|
-
getDecorators(node: ts.Node): readonly ts.Decorator[] | undefined;
|
|
199
|
-
canHaveModifiers(node: ts.Node): boolean;
|
|
200
|
-
getModifiers(node: ts.Node): readonly ts.Modifier[] | undefined;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const isBreakingTypeScriptApi = (compiler: object): compiler is BreakingTypeScriptApi =>
|
|
204
|
-
"canHaveDecorators" in compiler;
|