@eliasku/ts-transformers 0.0.2 → 0.0.4
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 +164 -17
- package/package.json +5 -3
- package/src/const-enum/evaluator.ts +0 -4
- package/src/const-enum/registry.ts +4 -5
- package/src/const-enum/utils.ts +7 -7
- package/src/exports/tracker.ts +6 -7
- package/src/index.ts +25 -25
- package/src/types.ts +5 -14
- package/src/typescript-helpers.ts +2 -2
package/README.md
CHANGED
|
@@ -1,44 +1,191 @@
|
|
|
1
1
|
# @eliasku/ts-transformers
|
|
2
2
|
|
|
3
|
-
TypeScript transformer for code
|
|
3
|
+
TypeScript transformer for aggressive code minification through type-aware property renaming and const enum inlining.
|
|
4
|
+
|
|
5
|
+
## Important Requirement
|
|
6
|
+
|
|
7
|
+
**You must compile ALL your code from TypeScript files.**
|
|
8
|
+
|
|
9
|
+
- No pre-transpiled `.js` files in source
|
|
10
|
+
- Transformer requires TypeScript type information
|
|
11
|
+
- Applicable for application builds, not libraries
|
|
12
|
+
|
|
13
|
+
## Core Concept
|
|
14
|
+
|
|
15
|
+
Two-phase optimization pipeline:
|
|
16
|
+
|
|
17
|
+
1. **This Transformer**: Analyzes TypeScript types, marks renamable properties with special prefixes
|
|
18
|
+
2. **Minifier (esbuild)**: Aggressively mangles prefixed properties while preserving public API
|
|
19
|
+
|
|
20
|
+
### Visibility Levels
|
|
21
|
+
|
|
22
|
+
Based on type analysis, properties are categorized as:
|
|
23
|
+
|
|
24
|
+
- **Public (External)**: Exported from entry points → **no prefix** (preserved)
|
|
25
|
+
- **Private**: Everything else → prefixed with `$_` (mangled by minifier)
|
|
26
|
+
|
|
27
|
+
**Example:**
|
|
28
|
+
```typescript
|
|
29
|
+
// Before
|
|
30
|
+
class MyClass {
|
|
31
|
+
/** @public - keeps name */
|
|
32
|
+
publicApi() {}
|
|
33
|
+
|
|
34
|
+
method() {} // Private → $_method
|
|
35
|
+
private secret = 1; // Private → $_secret
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// After transformer (before minifier)
|
|
39
|
+
class MyClass {
|
|
40
|
+
publicApi() {}
|
|
41
|
+
$_method() {}
|
|
42
|
+
$_secret = 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// After esbuild minifier
|
|
46
|
+
class A{publicApi(){},a(){},b=1}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Const Enum Inlining
|
|
50
|
+
|
|
51
|
+
Replaces const enum accesses with literal values and removes declarations.
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// Before
|
|
55
|
+
const enum Status { Active = 1, Inactive = 0 }
|
|
56
|
+
const status = Status.Active;
|
|
57
|
+
|
|
58
|
+
// After transformer + minifier
|
|
59
|
+
const status = 1;
|
|
60
|
+
```
|
|
4
61
|
|
|
5
62
|
## Usage
|
|
6
63
|
|
|
7
64
|
```typescript
|
|
8
65
|
import { optimizer } from "@eliasku/ts-transformers";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
66
|
+
import typescript from "@rollup/plugin-typescript";
|
|
67
|
+
import { rollup } from "rollup";
|
|
68
|
+
import { build } from "esbuild";
|
|
69
|
+
|
|
70
|
+
// Phase 1: Type-aware optimization
|
|
71
|
+
const bundle = await rollup({
|
|
72
|
+
input: "./src/index.ts",
|
|
73
|
+
plugins: [
|
|
74
|
+
typescript({
|
|
75
|
+
transformers: (program) => ({
|
|
76
|
+
before: [
|
|
77
|
+
optimizer(program, {
|
|
78
|
+
entrySourceFiles: ["./src/index.ts"],
|
|
79
|
+
inlineConstEnums: true,
|
|
80
|
+
}),
|
|
81
|
+
],
|
|
82
|
+
}),
|
|
15
83
|
}),
|
|
16
84
|
],
|
|
17
85
|
});
|
|
86
|
+
|
|
87
|
+
await bundle.write({
|
|
88
|
+
file: "./dist/bundle.js",
|
|
89
|
+
format: "es",
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Phase 2: Aggressive minification
|
|
93
|
+
await build({
|
|
94
|
+
entryPoints: ["./dist/bundle.js"],
|
|
95
|
+
outfile: "./dist/bundle.min.js",
|
|
96
|
+
minify: true,
|
|
97
|
+
mangleProps: /^\$_/, // Match your privatePrefix
|
|
98
|
+
mangleQuoted: false,
|
|
99
|
+
keepNames: false,
|
|
100
|
+
});
|
|
18
101
|
```
|
|
19
102
|
|
|
20
103
|
## Options
|
|
21
104
|
|
|
22
105
|
### entrySourceFiles (required)
|
|
23
106
|
|
|
24
|
-
|
|
107
|
+
Entry points defining your public API surface.
|
|
25
108
|
|
|
26
|
-
|
|
109
|
+
```typescript
|
|
110
|
+
entrySourceFiles: ["./src/index.ts"]
|
|
111
|
+
```
|
|
27
112
|
|
|
28
|
-
|
|
113
|
+
### privatePrefix (optional, default: "$_")
|
|
29
114
|
|
|
30
|
-
|
|
115
|
+
Prefix for private properties that will be mangled by esbuild.
|
|
31
116
|
|
|
32
|
-
|
|
117
|
+
```typescript
|
|
118
|
+
privatePrefix: "$_" // myFunction → $_myFunction
|
|
119
|
+
```
|
|
33
120
|
|
|
34
|
-
###
|
|
121
|
+
### publicJSDocTag (optional, default: "public")
|
|
35
122
|
|
|
36
|
-
|
|
123
|
+
JSDoc tag marking types/properties as public. Set to empty string to disable.
|
|
37
124
|
|
|
38
|
-
|
|
125
|
+
```typescript
|
|
126
|
+
publicJSDocTag: "public"
|
|
39
127
|
|
|
40
|
-
|
|
128
|
+
class MyClass {
|
|
129
|
+
/** @public */
|
|
130
|
+
apiMethod() {} // Public, no prefix
|
|
131
|
+
|
|
132
|
+
internalHelper() {} // Private, gets $_ prefix
|
|
133
|
+
}
|
|
134
|
+
```
|
|
41
135
|
|
|
42
136
|
### ignoreDecorated (optional, default: false)
|
|
43
137
|
|
|
44
|
-
|
|
138
|
+
Skip renaming decorated fields.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
ignoreDecorated: true
|
|
142
|
+
|
|
143
|
+
@Component({ selector: "app-root" })
|
|
144
|
+
class AppComponent {
|
|
145
|
+
@Input() data: any; // Not renamed
|
|
146
|
+
private internal = 1; // Renamed to $_internal
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### inlineConstEnums (optional, default: true)
|
|
151
|
+
|
|
152
|
+
Inline const enum values and remove declarations.
|
|
153
|
+
|
|
154
|
+
## Complete Example
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// src/index.ts (before)
|
|
158
|
+
class API {
|
|
159
|
+
private baseUrl = "https://api.example.com";
|
|
160
|
+
|
|
161
|
+
/** @public */
|
|
162
|
+
async get(path: string): Promise<Response> {
|
|
163
|
+
const url = `${this.baseUrl}${path}`;
|
|
164
|
+
const response = await fetch(url);
|
|
165
|
+
return this.handleResponse(response);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private async handleResponse(response: Response): Promise<Response> {
|
|
169
|
+
return response;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export const api = new API();
|
|
174
|
+
|
|
175
|
+
// After transformer
|
|
176
|
+
class API {
|
|
177
|
+
$_baseUrl = "https://api.example.com";
|
|
178
|
+
async get(path) {
|
|
179
|
+
const url = `${this.$_baseUrl}${path}`;
|
|
180
|
+
const response = await fetch(url);
|
|
181
|
+
return this.$_handleResponse(response);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
$_handleResponse(response) {
|
|
185
|
+
return response;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// After esbuild minifier
|
|
190
|
+
class A{a="https://api.example.com";async get(t){const n=`${this.a}${t}`;return await fetch(n)}b(t){return t}}const s=new A;export{s};
|
|
191
|
+
```
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eliasku/ts-transformers",
|
|
3
3
|
"description": "TypeScript transformer for code optimization",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.4",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "bun test",
|
|
9
9
|
"format": "prettier --write **/*.{ts,json,md,yml}",
|
|
10
10
|
"check": "tsc -p .",
|
|
11
|
-
"lint": "eslint ."
|
|
11
|
+
"lint": "eslint .",
|
|
12
|
+
"build:example": "cd example && bun run build.ts"
|
|
12
13
|
},
|
|
13
14
|
"dependencies": {
|
|
14
15
|
"typescript": "^5"
|
|
@@ -20,7 +21,8 @@
|
|
|
20
21
|
"@types/bun": "latest",
|
|
21
22
|
"rollup": "latest",
|
|
22
23
|
"@rollup/plugin-typescript": "latest",
|
|
23
|
-
"tslib": "latest"
|
|
24
|
+
"tslib": "latest",
|
|
25
|
+
"esbuild": "latest"
|
|
24
26
|
},
|
|
25
27
|
"module": "./src/index.ts",
|
|
26
28
|
"types": "./src/index.ts",
|
|
@@ -9,13 +9,9 @@ export interface EvaluationContext {
|
|
|
9
9
|
|
|
10
10
|
export class EnumEvaluator {
|
|
11
11
|
private lastImplicitValue = -1;
|
|
12
|
-
private enumType: "numeric" | "string" | "mixed" = "numeric";
|
|
13
|
-
|
|
14
|
-
constructor(private readonly typeChecker: ts.TypeChecker) {}
|
|
15
12
|
|
|
16
13
|
reset(): void {
|
|
17
14
|
this.lastImplicitValue = -1;
|
|
18
|
-
this.enumType = "numeric";
|
|
19
15
|
}
|
|
20
16
|
|
|
21
17
|
evaluate(expr: ts.Expression, context: EvaluationContext): EnumValue {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { EnumValue, EvaluationContext } from "./evaluator";
|
|
3
3
|
import { EnumEvaluator } from "./evaluator";
|
|
4
|
-
import {
|
|
4
|
+
import { isConstEnumSymbol } from "./utils";
|
|
5
|
+
import { hasModifier } from "../typescript-helpers";
|
|
5
6
|
|
|
6
7
|
export interface ConstEnumInfo {
|
|
7
8
|
declaration: ts.EnumDeclaration;
|
|
@@ -19,13 +20,11 @@ export interface ConstEnumMemberInfo {
|
|
|
19
20
|
export class ConstEnumRegistry {
|
|
20
21
|
private readonly program: ts.Program;
|
|
21
22
|
private readonly typeChecker: ts.TypeChecker;
|
|
22
|
-
private readonly entrySourceFiles: readonly string[];
|
|
23
23
|
private readonly enumDeclarations: Map<string, ConstEnumInfo>;
|
|
24
24
|
|
|
25
|
-
constructor(program: ts.Program
|
|
25
|
+
constructor(program: ts.Program) {
|
|
26
26
|
this.program = program;
|
|
27
27
|
this.typeChecker = program.getTypeChecker();
|
|
28
|
-
this.entrySourceFiles = entrySourceFiles || program.getRootFileNames();
|
|
29
28
|
this.enumDeclarations = new Map();
|
|
30
29
|
this.collectConstEnumsFromEntryPoints();
|
|
31
30
|
}
|
|
@@ -110,7 +109,7 @@ export class ConstEnumRegistry {
|
|
|
110
109
|
}
|
|
111
110
|
|
|
112
111
|
private evaluateEnumMembers(enumInfo: ConstEnumInfo): void {
|
|
113
|
-
const evaluator = new EnumEvaluator(
|
|
112
|
+
const evaluator = new EnumEvaluator();
|
|
114
113
|
evaluator.reset();
|
|
115
114
|
const context: EvaluationContext = {
|
|
116
115
|
localMembers: new Map(),
|
package/src/const-enum/utils.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
|
|
3
|
-
export const hasModifier = (node: ts.Node, modifier: ts.SyntaxKind) =>
|
|
4
|
-
ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((mod: ts.Modifier) => mod.kind === modifier);
|
|
5
|
-
|
|
6
3
|
export const isConstEnumSymbol = (symbol: ts.Symbol): boolean => (symbol.flags & ts.SymbolFlags.ConstEnum) !== 0;
|
|
7
4
|
|
|
8
5
|
export const isConstEnumType = (type: ts.Type | undefined): boolean => {
|
|
9
|
-
if (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
if (type) {
|
|
7
|
+
const symbol = type.symbol || type.aliasSymbol;
|
|
8
|
+
if (symbol) {
|
|
9
|
+
return (symbol.flags & ts.SymbolFlags.ConstEnum) !== 0;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
13
|
};
|
package/src/exports/tracker.ts
CHANGED
|
@@ -21,13 +21,12 @@ export class ExportsSymbolTree {
|
|
|
21
21
|
|
|
22
22
|
public isSymbolAccessibleFromExports(symbol: ts.Symbol): boolean {
|
|
23
23
|
symbol = this.getActualSymbol(symbol);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return result;
|
|
24
|
+
for (const [, set] of this.exportsTree) {
|
|
25
|
+
if (set.has(symbol)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
private computeTreeForExports(entrySourceFiles: readonly string[]): void {
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
+
export type { OptimizerOptions } from "./types";
|
|
2
3
|
|
|
3
4
|
import { ExportsSymbolTree } from "./exports/tracker";
|
|
4
5
|
import {
|
|
@@ -32,8 +33,8 @@ function createTransformerFactory(
|
|
|
32
33
|
const fullOptions: OptimizerOptions = { ...defaultOptions, ...options };
|
|
33
34
|
const typeChecker = program.getTypeChecker();
|
|
34
35
|
const exportsSymbolTree = new ExportsSymbolTree(program, fullOptions.entrySourceFiles);
|
|
35
|
-
const constEnumRegistry = new ConstEnumRegistry(program
|
|
36
|
-
const enumEvaluator = new EnumEvaluator(
|
|
36
|
+
const constEnumRegistry = new ConstEnumRegistry(program);
|
|
37
|
+
const enumEvaluator = new EnumEvaluator();
|
|
37
38
|
|
|
38
39
|
const cache = new Map<ts.Symbol, VisibilityType>();
|
|
39
40
|
|
|
@@ -43,7 +44,7 @@ function createTransformerFactory(
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
return (context: ts.TransformationContext) => {
|
|
46
|
-
function transformNode(node: ts.Node): ts.Node {
|
|
47
|
+
function transformNode(node: ts.Node): ts.Node | undefined {
|
|
47
48
|
if (fullOptions.inlineConstEnums !== false) {
|
|
48
49
|
if (ts.isPropertyAccessExpression(node)) {
|
|
49
50
|
const inlined = tryInlineConstEnum(node);
|
|
@@ -52,12 +53,16 @@ function createTransformerFactory(
|
|
|
52
53
|
|
|
53
54
|
if (ts.isImportSpecifier(node)) {
|
|
54
55
|
const removed = tryRemoveConstEnumImport(node);
|
|
55
|
-
if (removed === undefined)
|
|
56
|
+
if (removed === undefined) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
if (ts.isImportClause(node)) {
|
|
59
62
|
const removed = tryRemoveConstEnumImportClause(node);
|
|
60
|
-
if (removed === undefined)
|
|
63
|
+
if (removed === undefined) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
|
|
@@ -70,9 +75,8 @@ function createTransformerFactory(
|
|
|
70
75
|
if (ts.isBindingElement(node) && node.propertyName === undefined) {
|
|
71
76
|
if (node.parent && ts.isObjectBindingPattern(node.parent)) {
|
|
72
77
|
return handleShorthandObjectBindingElement(node);
|
|
73
|
-
} else {
|
|
74
|
-
console.warn("!!!", node);
|
|
75
78
|
}
|
|
79
|
+
return node;
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
// is not supported:
|
|
@@ -156,7 +160,7 @@ function createTransformerFactory(
|
|
|
156
160
|
return node;
|
|
157
161
|
}
|
|
158
162
|
|
|
159
|
-
return createNewNode(propertyName, VisibilityType.
|
|
163
|
+
return createNewNode(propertyName, VisibilityType.Private, context.factory.createStringLiteral);
|
|
160
164
|
}
|
|
161
165
|
|
|
162
166
|
// obj.node
|
|
@@ -266,14 +270,12 @@ function createTransformerFactory(
|
|
|
266
270
|
type: VisibilityType,
|
|
267
271
|
createNode: (newName: string) => T,
|
|
268
272
|
): T {
|
|
269
|
-
const newPropertyName = getNewName(oldPropertyName
|
|
273
|
+
const newPropertyName = getNewName(oldPropertyName);
|
|
270
274
|
return createNode(newPropertyName);
|
|
271
275
|
}
|
|
272
276
|
|
|
273
|
-
function getNewName(originalName: string
|
|
274
|
-
return `${
|
|
275
|
-
type === VisibilityType.Private ? fullOptions.privatePrefix : fullOptions.internalPrefix
|
|
276
|
-
}${originalName}`;
|
|
277
|
+
function getNewName(originalName: string): string {
|
|
278
|
+
return `${fullOptions.privatePrefix}${originalName}`;
|
|
277
279
|
}
|
|
278
280
|
|
|
279
281
|
function getActualSymbol(symbol: ts.Symbol): ts.Symbol {
|
|
@@ -548,7 +550,6 @@ function createTransformerFactory(
|
|
|
548
550
|
}
|
|
549
551
|
|
|
550
552
|
if (nodeSymbol.escapedName === "prototype") {
|
|
551
|
-
// accessing to prototype
|
|
552
553
|
return putToCache(nodeSymbol, VisibilityType.External);
|
|
553
554
|
}
|
|
554
555
|
|
|
@@ -600,7 +601,7 @@ function createTransformerFactory(
|
|
|
600
601
|
}
|
|
601
602
|
}
|
|
602
603
|
|
|
603
|
-
return putToCache(nodeSymbol, VisibilityType.
|
|
604
|
+
return putToCache(nodeSymbol, VisibilityType.Private);
|
|
604
605
|
}
|
|
605
606
|
|
|
606
607
|
function getShorthandObjectBindingElementSymbol(element: ts.BindingElement): ts.Symbol | null {
|
|
@@ -696,22 +697,21 @@ function createTransformerFactory(
|
|
|
696
697
|
return undefined;
|
|
697
698
|
}
|
|
698
699
|
|
|
699
|
-
function wrapTransformNode(node: ts.Node): ts.Node {
|
|
700
|
+
function wrapTransformNode(node: ts.Node): ts.Node | undefined {
|
|
700
701
|
if (ts.isEnumDeclaration(node)) {
|
|
701
702
|
const result = handleEnumDeclaration(node);
|
|
702
|
-
if (result === undefined)
|
|
703
|
-
|
|
703
|
+
if (result === undefined) {
|
|
704
|
+
return undefined;
|
|
705
|
+
}
|
|
706
|
+
if (result !== node) {
|
|
707
|
+
return result;
|
|
708
|
+
}
|
|
704
709
|
}
|
|
705
710
|
return transformNode(node);
|
|
706
711
|
}
|
|
707
712
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
wrapTransformNode(node),
|
|
711
|
-
(childNode: ts.Node) => wrappedTransformNodeAndChildren(childNode),
|
|
712
|
-
context,
|
|
713
|
-
);
|
|
714
|
-
}
|
|
713
|
+
const wrappedTransformNodeAndChildren = (node: ts.Node): ts.Node | undefined =>
|
|
714
|
+
ts.visitEachChild(wrapTransformNode(node), wrappedTransformNodeAndChildren, context);
|
|
715
715
|
|
|
716
716
|
return wrappedTransformNodeAndChildren(sourceFile) as ts.SourceFile;
|
|
717
717
|
};
|
package/src/types.ts
CHANGED
|
@@ -8,18 +8,11 @@ export interface OptimizerOptions {
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Prefix of generated names for private fields
|
|
11
|
-
* @example '_private_'
|
|
12
|
-
* @example '$
|
|
11
|
+
* @example '_private_'
|
|
12
|
+
* @example '$_' // default
|
|
13
13
|
*/
|
|
14
14
|
privatePrefix: string;
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
* Prefix of generated names for internal fields
|
|
18
|
-
* @example '_internal_' // default
|
|
19
|
-
* @example '$i$'
|
|
20
|
-
*/
|
|
21
|
-
internalPrefix: string;
|
|
22
|
-
|
|
23
16
|
/**
|
|
24
17
|
* Comment which will treat a class/interface/type/property/etc and all its children as "public".
|
|
25
18
|
* Set it to empty string to disable using JSDoc comment to detecting "visibility level".
|
|
@@ -42,15 +35,13 @@ export interface OptimizerOptions {
|
|
|
42
35
|
}
|
|
43
36
|
|
|
44
37
|
export const enum VisibilityType {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
External = 2,
|
|
38
|
+
Private = 0,
|
|
39
|
+
External = 1,
|
|
48
40
|
}
|
|
49
41
|
|
|
50
42
|
export const defaultOptions: OptimizerOptions = {
|
|
51
43
|
entrySourceFiles: [],
|
|
52
|
-
privatePrefix: "$
|
|
53
|
-
internalPrefix: "$i$",
|
|
44
|
+
privatePrefix: "$_",
|
|
54
45
|
publicJSDocTag: "public",
|
|
55
46
|
ignoreDecorated: false,
|
|
56
47
|
inlineConstEnums: true,
|
|
@@ -139,8 +139,8 @@ function getModifiers(node: ts.Node): readonly ts.Modifier[] {
|
|
|
139
139
|
return node.modifiers || [];
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
export const hasModifier = (node: ts.Node, modifier: ts.SyntaxKind) =>
|
|
143
|
-
getModifiers(node)
|
|
142
|
+
export const hasModifier = (node: ts.Node, modifier: ts.SyntaxKind): boolean =>
|
|
143
|
+
ts.canHaveModifiers(node) && getModifiers(node)?.some((mod) => mod.kind === modifier);
|
|
144
144
|
|
|
145
145
|
function getDecorators(node: ts.Node): readonly unknown[] {
|
|
146
146
|
if (isBreakingTypeScriptApi(ts)) {
|