restfuncs-transformer 0.9.7 → 1.0.0
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/AddRemoteMethodsMeta.d.ts +67 -0
- package/AddRemoteMethodsMeta.d.ts.map +1 -0
- package/AddRemoteMethodsMeta.js +221 -0
- package/AddRemoteMethodsMeta.js.map +1 -0
- package/AddRemoteMethodsMeta.ts +403 -0
- package/devPlayground.d.ts +2 -0
- package/devPlayground.d.ts.map +1 -0
- package/devPlayground.js +31 -0
- package/devPlayground.js.map +1 -0
- package/devPlayground.ts +15 -0
- package/index.d.ts +7 -2
- package/index.d.ts.map +1 -1
- package/index.js +58 -29
- package/index.js.map +1 -1
- package/index.ts +89 -4
- package/package.json +10 -6
- package/readme.md +43 -2
- package/transformerUtil.d.ts +50 -0
- package/transformerUtil.d.ts.map +1 -0
- package/transformerUtil.js +104 -0
- package/transformerUtil.js.map +1 -0
- package/transformerUtil.ts +104 -0
package/index.ts
CHANGED
|
@@ -1,4 +1,89 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
1
|
+
import ts, {CompilerHost, CompilerOptions, Program, SourceFile} from 'typescript';
|
|
2
|
+
import {PluginConfig, ProgramTransformerExtras} from "ts-patch";
|
|
3
|
+
import {AddRemoteMethodsMeta} from "./AddRemoteMethodsMeta";
|
|
4
|
+
import {FileTransformRun, TransformerFactoryOOP} from "./transformerUtil";
|
|
5
|
+
|
|
6
|
+
// From: https://github.com/nonara/ts-patch/discussions/29#discussioncomment-325979
|
|
7
|
+
|
|
8
|
+
export const transformerVersion = {major: 1, feature: 1 }
|
|
9
|
+
|
|
10
|
+
/* ****************************************************************************************************************** */
|
|
11
|
+
// region: Helpers
|
|
12
|
+
/* ****************************************************************************************************************** */
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Patches existing Compiler Host (or creates new one) to allow feeding updated file content from cache
|
|
16
|
+
*/
|
|
17
|
+
function getPatchedHost(
|
|
18
|
+
maybeHost: CompilerHost | undefined,
|
|
19
|
+
tsInstance: typeof ts,
|
|
20
|
+
compilerOptions: CompilerOptions
|
|
21
|
+
): CompilerHost & { fileCache: Map<string, SourceFile> }
|
|
22
|
+
{
|
|
23
|
+
const fileCache = new Map();
|
|
24
|
+
const compilerHost = maybeHost ?? tsInstance.createCompilerHost(compilerOptions, true);
|
|
25
|
+
const originalGetSourceFile = compilerHost.getSourceFile;
|
|
26
|
+
|
|
27
|
+
return Object.assign(compilerHost, {
|
|
28
|
+
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget) {
|
|
29
|
+
fileName = tsInstance.normalizePath(fileName);
|
|
30
|
+
if (fileCache.has(fileName)) return fileCache.get(fileName);
|
|
31
|
+
|
|
32
|
+
const sourceFile = originalGetSourceFile.apply(void 0, Array.from(arguments) as any);
|
|
33
|
+
fileCache.set(fileName, sourceFile);
|
|
34
|
+
|
|
35
|
+
return sourceFile;
|
|
36
|
+
},
|
|
37
|
+
fileCache
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// endregion
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
/* ****************************************************************************************************************** */
|
|
45
|
+
// region: Program Transformer
|
|
46
|
+
/* ****************************************************************************************************************** */
|
|
47
|
+
|
|
48
|
+
export default function transformProgram(
|
|
49
|
+
program: Program,
|
|
50
|
+
host: CompilerHost | undefined,
|
|
51
|
+
config: PluginConfig,
|
|
52
|
+
extras?: ProgramTransformerExtras,
|
|
53
|
+
): Program {
|
|
54
|
+
if(!extras) {
|
|
55
|
+
throw new Error(`Please add the flag "transformProgram": true to the transformer inside tsconfig.json`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const { ts: tsInstance } = extras;
|
|
59
|
+
const compilerOptions = program.getCompilerOptions();
|
|
60
|
+
const compilerHost = getPatchedHost(host, tsInstance, compilerOptions);
|
|
61
|
+
const rootFileNames = program.getRootFileNames().map(tsInstance.normalizePath);
|
|
62
|
+
|
|
63
|
+
const transformerFactoryOOP = new TransformerFactoryOOP(AddRemoteMethodsMeta);
|
|
64
|
+
|
|
65
|
+
/* Transform AST */
|
|
66
|
+
tsInstance.transform(
|
|
67
|
+
/* sourceFiles */ program.getSourceFiles().filter(sourceFile => rootFileNames.includes(sourceFile.fileName)),
|
|
68
|
+
/* transformerFactoryOOP */ [ transformerFactoryOOP.asFunction ],
|
|
69
|
+
compilerOptions
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
transformerFactoryOOP.transformRunsDone.forEach(transformRun => {
|
|
73
|
+
if(transformRun.astWasModified) {
|
|
74
|
+
/* Render modified files and create new SourceFiles for them to use in host's cache */
|
|
75
|
+
const {printFile} = tsInstance.createPrinter();
|
|
76
|
+
const sourceFile = transformRun.sourceFile;
|
|
77
|
+
const updatedSourceFile = tsInstance.createSourceFile(sourceFile.fileName, printFile(sourceFile), sourceFile.languageVersion);
|
|
78
|
+
updatedSourceFile.version = `${sourceFile.version}_restfuncs_${transformerVersion.major}.${transformerVersion.feature}`;
|
|
79
|
+
compilerHost.fileCache.set(sourceFile.fileName, updatedSourceFile);
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
/* Re-create Program instance */
|
|
86
|
+
return tsInstance.createProgram(rootFileNames, compilerOptions, compilerHost);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "restfuncs-transformer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Enhances your classes with typescript-rtti, to make them usable with Restfuncs",
|
|
5
5
|
"keywords": ["rpc","rest"],
|
|
6
6
|
"author": "Boris Gingold <bogeee@bogitech.de>",
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
},
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"scripts": {
|
|
14
|
-
"clean": "tsc --build --clean &&
|
|
15
|
-
"build": "tsc"
|
|
14
|
+
"clean": "tsc --build --clean && rimraf dist LICENSE",
|
|
15
|
+
"build": "tsc --build",
|
|
16
|
+
"dev:playgrund": "tsx devPlayground.ts"
|
|
16
17
|
},
|
|
17
18
|
"exports": {
|
|
18
19
|
".": {
|
|
@@ -20,10 +21,13 @@
|
|
|
20
21
|
}
|
|
21
22
|
},
|
|
22
23
|
"dependencies": {
|
|
23
|
-
"typescript
|
|
24
|
+
"typescript": "5.0 || 5.1 || 5.3"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"restfuncs-server": ">=2"
|
|
24
28
|
},
|
|
25
29
|
"devDependencies": {
|
|
26
|
-
"
|
|
27
|
-
"
|
|
30
|
+
"@types/ts-expose-internals": "npm:ts-expose-internals@5.3.3",
|
|
31
|
+
"rimraf": "=5.0.5"
|
|
28
32
|
}
|
|
29
33
|
}
|
package/readme.md
CHANGED
|
@@ -1,2 +1,43 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# restfuncs-transformer
|
|
2
|
+
This transformer prepares `ServerSession` subclasses for Typia and adds jsDoc info for the (upcoming) API browser.
|
|
3
|
+
|
|
4
|
+
## Usage
|
|
5
|
+
[Here's how to use it](https://github.com/bogeeee/restfuncs/tree/3.x/readme.md#setting-up-the-build-here-it-gets-a-bit-nasty-)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## How the transformer chain works
|
|
9
|
+
Here is, how the restfuncs-transformer, typia transformer and typescript-rtti work together:
|
|
10
|
+
1. The `restfuncs-transformer` scans for all `@remote` methods and, at the bottom of inside their class, it adds the following code (here for "myMethod"):
|
|
11
|
+
````typescript
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Code is generated by the restfuncs-transformer during compile-time
|
|
15
|
+
*/
|
|
16
|
+
static getRemoteMethodsMeta(): (typeof this.type_remoteMethodsMeta) {
|
|
17
|
+
this.__hello_developer__make_sure_your_class_is_a_subclass_of_ServerSession // Give a friendly error message when this is not the case. Otherwise the following statement "const typia = ..." would fail and leaves the user wondering.
|
|
18
|
+
let typia = this.typiaRuntime; // We need a "typia" defined in the scope, but let restfuncs manage where that dependency comes from
|
|
19
|
+
return {
|
|
20
|
+
transformerVersion: {major: XX, feature: XX },
|
|
21
|
+
instanceMethods: {
|
|
22
|
+
"myMethod": {
|
|
23
|
+
arguments: {
|
|
24
|
+
validateEquals: (args: unknown) => typia.validateEquals<Parameters<typeof this.prototype["myMethod"]>>(args),
|
|
25
|
+
validatePrune: ...
|
|
26
|
+
},
|
|
27
|
+
result: {
|
|
28
|
+
validateEquals: (value: unknown) => typia.validateEquals<Awaited<ReturnType<typeof this.prototype["myMethod"]>>>(value),
|
|
29
|
+
...
|
|
30
|
+
},
|
|
31
|
+
jsDoc: {
|
|
32
|
+
comment: "text with <strong>markup</strong>",
|
|
33
|
+
params: {a: "text..."},
|
|
34
|
+
tags: [{name:"returns", comment: "text..."}]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
````
|
|
41
|
+
|
|
42
|
+
2. The [Typia transformer](https://typia.io/) will then replace all `typia.validate<T>` expressions with the actual validation code body. Typia calls this "ahead of time validation". At runtime this is used to validate evil input and serialize/deserialize from json/protobuf in an ultra fast way (future).
|
|
43
|
+
3. The [typescript-rtti transformer](https://typescript-rtti.org/) adds type-info decorations for all compiled symbols (that's all this `__RΦ` stuff in the .js). At runtime, this allows restfuncs to easily navigate through- and inspect all the type hierarchy. I.e when mapping REST parameter names to the remote methods arguments, for better error diagnosis, or for the upcoming API browser.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import ts, { Node, SourceFile, TransformationContext, TransformerFactory } from "typescript";
|
|
2
|
+
type ClassOf<T> = {
|
|
3
|
+
new (...args: any[]): T;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Transformerfactory in a better oop style
|
|
7
|
+
*/
|
|
8
|
+
export declare class TransformerFactoryOOP<FT extends FileTransformRun> {
|
|
9
|
+
transformRunsDone: FT[];
|
|
10
|
+
fileTransformRunClass: ClassOf<FileTransformRun>;
|
|
11
|
+
constructor(fileTransformerRunClass: ClassOf<FT>);
|
|
12
|
+
/**
|
|
13
|
+
* Transformer for the typescript api (does not offer such a nice oop style)
|
|
14
|
+
*/
|
|
15
|
+
get asFunction(): TransformerFactory<SourceFile>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Stores the context and result of one run. Don't reuse.
|
|
19
|
+
*/
|
|
20
|
+
export declare abstract class FileTransformRun {
|
|
21
|
+
tsInstance: typeof ts;
|
|
22
|
+
sourceFile: SourceFile;
|
|
23
|
+
context: TransformationContext;
|
|
24
|
+
parentNodes: Node[];
|
|
25
|
+
used: boolean;
|
|
26
|
+
constructor(tsInstance: typeof ts, sourceFile: SourceFile, context: TransformationContext);
|
|
27
|
+
getParent: () => ts.Node;
|
|
28
|
+
/**
|
|
29
|
+
* Vistor function
|
|
30
|
+
* The body must call visitChilds, if the subtree should be visited
|
|
31
|
+
* @param node
|
|
32
|
+
*/
|
|
33
|
+
abstract visit(node: Node): Node;
|
|
34
|
+
protected visitChilds(node: Node): ts.Node;
|
|
35
|
+
/**
|
|
36
|
+
* @return text of the node (only for string literals, etc)
|
|
37
|
+
* @param node
|
|
38
|
+
*/
|
|
39
|
+
getText(node: Node): string;
|
|
40
|
+
getChilds(node: Node): ts.Node[];
|
|
41
|
+
/**
|
|
42
|
+
*
|
|
43
|
+
* @param tsInstance
|
|
44
|
+
* @param node
|
|
45
|
+
* @param escaped don't know why but sometimes the name can only be read by the escapedText property.
|
|
46
|
+
*/
|
|
47
|
+
getIdentifierName(node: Node, escaped?: boolean): any;
|
|
48
|
+
}
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=transformerUtil.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transformerUtil.d.ts","sourceRoot":"","sources":["transformerUtil.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAC,IAAI,EAAE,UAAU,EAAc,qBAAqB,EAAE,kBAAkB,EAAC,MAAM,YAAY,CAAC;AAEvG,KAAK,OAAO,CAAC,CAAC,IAAI;IACd,KAAI,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;CACzB,CAAA;AAED;;GAEG;AACH,qBAAa,qBAAqB,CAAC,EAAE,SAAS,gBAAgB;IAC1D,iBAAiB,EAAE,EAAE,EAAE,CAAK;IAC5B,qBAAqB,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;gBAGrC,uBAAuB,EAAE,OAAO,CAAC,EAAE,CAAC;IAIhD;;OAEG;IACH,IAAI,UAAU,IAAI,kBAAkB,CAAC,UAAU,CAAC,CAQ/C;CACJ;AAED;;GAEG;AACH,8BAAsB,gBAAgB;IAClC,UAAU,EAAE,OAAO,EAAE,CAAC;IACtB,UAAU,EAAE,UAAU,CAAA;IACtB,OAAO,EAAE,qBAAqB,CAAC;IAC/B,WAAW,EAAE,IAAI,EAAE,CAAK;IACxB,IAAI,UAAS;gBAED,UAAU,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,qBAAqB;IAOzF,SAAS,gBAEP;IAEF;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAGhC,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI;IAWhC;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM;IAK3B,SAAS,CAAC,IAAI,EAAE,IAAI;IAMpB;;;;;OAKG;IACH,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,UAAQ;CAUhD"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.FileTransformRun = exports.TransformerFactoryOOP = void 0;
|
|
27
|
+
const typescript_1 = __importStar(require("typescript"));
|
|
28
|
+
/**
|
|
29
|
+
* Transformerfactory in a better oop style
|
|
30
|
+
*/
|
|
31
|
+
class TransformerFactoryOOP {
|
|
32
|
+
constructor(fileTransformerRunClass) {
|
|
33
|
+
this.transformRunsDone = [];
|
|
34
|
+
this.fileTransformRunClass = fileTransformerRunClass;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Transformer for the typescript api (does not offer such a nice oop style)
|
|
38
|
+
*/
|
|
39
|
+
get asFunction() {
|
|
40
|
+
return (context) => {
|
|
41
|
+
return (sourceFile) => {
|
|
42
|
+
const fileTransformer = new this.fileTransformRunClass(typescript_1.default, sourceFile, context);
|
|
43
|
+
this.transformRunsDone.push(fileTransformer);
|
|
44
|
+
return typescript_1.default.visitEachChild(sourceFile, (node) => fileTransformer.visit(node), context);
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.TransformerFactoryOOP = TransformerFactoryOOP;
|
|
50
|
+
/**
|
|
51
|
+
* Stores the context and result of one run. Don't reuse.
|
|
52
|
+
*/
|
|
53
|
+
class FileTransformRun {
|
|
54
|
+
constructor(tsInstance, sourceFile, context) {
|
|
55
|
+
this.parentNodes = [];
|
|
56
|
+
this.used = false;
|
|
57
|
+
this.getParent = () => {
|
|
58
|
+
return this.parentNodes[this.parentNodes.length - 1];
|
|
59
|
+
};
|
|
60
|
+
this.tsInstance = tsInstance;
|
|
61
|
+
this.sourceFile = sourceFile;
|
|
62
|
+
this.context = context;
|
|
63
|
+
}
|
|
64
|
+
visitChilds(node) {
|
|
65
|
+
this.parentNodes.push(node);
|
|
66
|
+
try {
|
|
67
|
+
return this.tsInstance.visitEachChild(node, (node) => this.visit(node), this.context);
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
this.parentNodes.pop();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* @return text of the node (only for string literals, etc)
|
|
75
|
+
* @param node
|
|
76
|
+
*/
|
|
77
|
+
getText(node) {
|
|
78
|
+
// @ts-ignore
|
|
79
|
+
return node.text;
|
|
80
|
+
}
|
|
81
|
+
getChilds(node) {
|
|
82
|
+
let result = [];
|
|
83
|
+
this.tsInstance.forEachChild(node, child => result.push(child));
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
*
|
|
88
|
+
* @param tsInstance
|
|
89
|
+
* @param node
|
|
90
|
+
* @param escaped don't know why but sometimes the name can only be read by the escapedText property.
|
|
91
|
+
*/
|
|
92
|
+
getIdentifierName(node, escaped = false) {
|
|
93
|
+
const identifierNode = this.getChilds(node).find(child => child.kind == typescript_1.SyntaxKind.Identifier);
|
|
94
|
+
if (!identifierNode) {
|
|
95
|
+
throw new Error("Identifier node not found");
|
|
96
|
+
}
|
|
97
|
+
if (escaped) {
|
|
98
|
+
return identifierNode.escapedText;
|
|
99
|
+
}
|
|
100
|
+
return identifierNode.getText();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
exports.FileTransformRun = FileTransformRun;
|
|
104
|
+
//# sourceMappingURL=transformerUtil.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transformerUtil.js","sourceRoot":"","sources":["transformerUtil.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,yDAAuG;AAMvG;;GAEG;AACH,MAAa,qBAAqB;IAK9B,YAAY,uBAAoC;QAJhD,sBAAiB,GAAS,EAAE,CAAA;QAKxB,IAAI,CAAC,qBAAqB,GAAG,uBAAuB,CAAC;IACzD,CAAC;IAED;;OAEG;IACH,IAAI,UAAU;QACV,OAAO,CAAC,OAA8B,EAAE,EAAE;YACtC,OAAO,CAAC,UAAsB,EAAc,EAAE;gBAC1C,MAAM,eAAe,GAAG,IAAI,IAAI,CAAC,qBAAqB,CAAC,oBAAE,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;gBAChF,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,eAAqB,CAAC,CAAC;gBACnD,OAAO,oBAAE,CAAC,cAAc,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;YACzF,CAAC,CAAA;QACL,CAAC,CAAA;IACL,CAAC;CACJ;AArBD,sDAqBC;AAED;;GAEG;AACH,MAAsB,gBAAgB;IAOlC,YAAY,UAAqB,EAAE,UAAsB,EAAE,OAA8B;QAHzF,gBAAW,GAAW,EAAE,CAAA;QACxB,SAAI,GAAG,KAAK,CAAC;QASb,cAAS,GAAG,GAAG,EAAE;YACb,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACzD,CAAC,CAAC;QARE,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAC3B,CAAC;IAeS,WAAW,CAAC,IAAU;QAC5B,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI;YACA,OAAO,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;SACzF;gBAAS;YACN,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC;SAC1B;IACL,CAAC;IAID;;;OAGG;IACH,OAAO,CAAC,IAAU;QACd,aAAa;QACb,OAAO,IAAI,CAAC,IAAI,CAAC;IACrB,CAAC;IAED,SAAS,CAAC,IAAU;QAChB,IAAI,MAAM,GAAW,EAAE,CAAC;QACxB,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAChE,OAAO,MAAM,CAAA;IACjB,CAAC;IAED;;;;;OAKG;IACH,iBAAiB,CAAC,IAAU,EAAE,OAAO,GAAG,KAAK;QACzC,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,IAAI,uBAAU,CAAC,UAAU,CAAC,CAAC;QAC/F,IAAG,CAAC,cAAc,EAAE;YAChB,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAA;SAC/C;QACD,IAAG,OAAO,EAAE;YACR,OAAQ,cAAsB,CAAC,WAAW,CAAC;SAC9C;QACD,OAAO,cAAc,CAAC,OAAO,EAAE,CAAC;IACpC,CAAC;CACJ;AApED,4CAoEC"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import ts, {Node, SourceFile, SyntaxKind, TransformationContext, TransformerFactory} from "typescript";
|
|
2
|
+
|
|
3
|
+
type ClassOf<T> = {
|
|
4
|
+
new(...args: any[]): T
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Transformerfactory in a better oop style
|
|
9
|
+
*/
|
|
10
|
+
export class TransformerFactoryOOP<FT extends FileTransformRun> {
|
|
11
|
+
transformRunsDone: FT[] = []
|
|
12
|
+
fileTransformRunClass: ClassOf<FileTransformRun>;
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
constructor(fileTransformerRunClass: ClassOf<FT>) {
|
|
16
|
+
this.fileTransformRunClass = fileTransformerRunClass;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Transformer for the typescript api (does not offer such a nice oop style)
|
|
21
|
+
*/
|
|
22
|
+
get asFunction(): TransformerFactory<SourceFile> {
|
|
23
|
+
return (context: TransformationContext) => {
|
|
24
|
+
return (sourceFile: SourceFile): SourceFile => {
|
|
25
|
+
const fileTransformer = new this.fileTransformRunClass(ts, sourceFile, context);
|
|
26
|
+
this.transformRunsDone.push(fileTransformer as FT);
|
|
27
|
+
return ts.visitEachChild(sourceFile, (node) => fileTransformer.visit(node), context);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Stores the context and result of one run. Don't reuse.
|
|
35
|
+
*/
|
|
36
|
+
export abstract class FileTransformRun {
|
|
37
|
+
tsInstance: typeof ts;
|
|
38
|
+
sourceFile: SourceFile
|
|
39
|
+
context: TransformationContext;
|
|
40
|
+
parentNodes: Node[] = []
|
|
41
|
+
used = false;
|
|
42
|
+
|
|
43
|
+
constructor(tsInstance: typeof ts, sourceFile: SourceFile, context: TransformationContext) {
|
|
44
|
+
this.tsInstance = tsInstance;
|
|
45
|
+
this.sourceFile = sourceFile;
|
|
46
|
+
this.context = context;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
getParent = () => {
|
|
51
|
+
return this.parentNodes[this.parentNodes.length - 1];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Vistor function
|
|
56
|
+
* The body must call visitChilds, if the subtree should be visited
|
|
57
|
+
* @param node
|
|
58
|
+
*/
|
|
59
|
+
abstract visit(node: Node): Node;
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
protected visitChilds(node: Node) {
|
|
63
|
+
this.parentNodes.push(node);
|
|
64
|
+
try {
|
|
65
|
+
return this.tsInstance.visitEachChild(node, (node) => this.visit(node), this.context);
|
|
66
|
+
} finally {
|
|
67
|
+
this.parentNodes.pop();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @return text of the node (only for string literals, etc)
|
|
75
|
+
* @param node
|
|
76
|
+
*/
|
|
77
|
+
getText(node: Node): string {
|
|
78
|
+
// @ts-ignore
|
|
79
|
+
return node.text;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getChilds(node: Node) {
|
|
83
|
+
let result: Node[] = [];
|
|
84
|
+
this.tsInstance.forEachChild(node, child => result.push(child));
|
|
85
|
+
return result
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
*
|
|
90
|
+
* @param tsInstance
|
|
91
|
+
* @param node
|
|
92
|
+
* @param escaped don't know why but sometimes the name can only be read by the escapedText property.
|
|
93
|
+
*/
|
|
94
|
+
getIdentifierName(node: Node, escaped = false) {
|
|
95
|
+
const identifierNode = this.getChilds(node).find(child => child.kind == SyntaxKind.Identifier);
|
|
96
|
+
if(!identifierNode) {
|
|
97
|
+
throw new Error("Identifier node not found")
|
|
98
|
+
}
|
|
99
|
+
if(escaped) {
|
|
100
|
+
return (identifierNode as any).escapedText;
|
|
101
|
+
}
|
|
102
|
+
return identifierNode.getText();
|
|
103
|
+
}
|
|
104
|
+
}
|