restfuncs-transformer 0.9.8 → 1.1.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 +85 -0
- package/AddRemoteMethodsMeta.d.ts.map +1 -0
- package/AddRemoteMethodsMeta.js +348 -0
- package/AddRemoteMethodsMeta.js.map +1 -0
- package/AddRemoteMethodsMeta.ts +481 -0
- package/devPlayground.d.ts +3 -0
- package/devPlayground.d.ts.map +1 -0
- package/devPlayground.js +3 -0
- package/devPlayground.js.map +1 -0
- package/devPlayground.ts +5 -0
- package/index.d.ts +7 -2
- package/index.d.ts.map +1 -1
- package/index.js +93 -28
- package/index.js.map +1 -1
- package/index.ts +105 -4
- package/package.json +14 -6
- package/readme.md +70 -3
- package/transformerUtil.d.ts +60 -0
- package/transformerUtil.d.ts.map +1 -0
- package/transformerUtil.js +126 -0
- package/transformerUtil.js.map +1 -0
- package/transformerUtil.test.d.ts +2 -0
- package/transformerUtil.test.d.ts.map +1 -0
- package/transformerUtil.test.js +11 -0
- package/transformerUtil.test.js.map +1 -0
- package/transformerUtil.test.ts +12 -0
- package/transformerUtil.ts +127 -0
package/index.ts
CHANGED
|
@@ -1,4 +1,105 @@
|
|
|
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
|
+
import * as fs from "node:fs";
|
|
6
|
+
|
|
7
|
+
// From: https://github.com/nonara/ts-patch/discussions/29#discussioncomment-325979
|
|
8
|
+
|
|
9
|
+
export const transformerVersion = {major: 1, feature: 2 }
|
|
10
|
+
|
|
11
|
+
/* ****************************************************************************************************************** */
|
|
12
|
+
// region: Helpers
|
|
13
|
+
/* ****************************************************************************************************************** */
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Patches existing Compiler Host (or creates new one) to allow feeding updated file content from cache
|
|
17
|
+
*/
|
|
18
|
+
function getPatchedHost(
|
|
19
|
+
maybeHost: CompilerHost | undefined,
|
|
20
|
+
tsInstance: typeof ts,
|
|
21
|
+
compilerOptions: CompilerOptions
|
|
22
|
+
): CompilerHost & { fileCache: Map<string, SourceFile> }
|
|
23
|
+
{
|
|
24
|
+
const fileCache = new Map();
|
|
25
|
+
const compilerHost = maybeHost ?? tsInstance.createCompilerHost(compilerOptions, true);
|
|
26
|
+
const originalGetSourceFile = compilerHost.getSourceFile;
|
|
27
|
+
|
|
28
|
+
return Object.assign(compilerHost, {
|
|
29
|
+
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget) {
|
|
30
|
+
fileName = tsInstance.normalizePath(fileName);
|
|
31
|
+
if (fileCache.has(fileName)) return fileCache.get(fileName);
|
|
32
|
+
|
|
33
|
+
const sourceFile = originalGetSourceFile.apply(void 0, Array.from(arguments) as any);
|
|
34
|
+
fileCache.set(fileName, sourceFile);
|
|
35
|
+
|
|
36
|
+
return sourceFile;
|
|
37
|
+
},
|
|
38
|
+
fileCache
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// endregion
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
/* ****************************************************************************************************************** */
|
|
46
|
+
// region: Program Transformer
|
|
47
|
+
/* ****************************************************************************************************************** */
|
|
48
|
+
|
|
49
|
+
export default function transformProgram(
|
|
50
|
+
program: Program,
|
|
51
|
+
host: CompilerHost | undefined,
|
|
52
|
+
config: PluginConfig,
|
|
53
|
+
extras?: ProgramTransformerExtras,
|
|
54
|
+
): Program {
|
|
55
|
+
if(!extras) {
|
|
56
|
+
throw new Error(`Please add the flag "transformProgram": true to the transformer inside tsconfig.json`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const { ts: tsInstance } = extras;
|
|
60
|
+
const compilerOptions = program.getCompilerOptions();
|
|
61
|
+
const compilerHost = getPatchedHost(host, tsInstance, compilerOptions);
|
|
62
|
+
const rootFileNames = program.getRootFileNames().map(tsInstance.normalizePath);
|
|
63
|
+
|
|
64
|
+
const transformerFactoryOOP = new TransformerFactoryOOP(AddRemoteMethodsMeta);
|
|
65
|
+
AddRemoteMethodsMeta.squeezeDeclarationsIntoOneLine = !((config as any).pretty);
|
|
66
|
+
|
|
67
|
+
// TODO: Do we still need all this transformer stuff or can we throw that out and use a simple file vistor ? Cause we don't actually **transform** a SourceFile anymore, but just patch text content and not an AST.
|
|
68
|
+
|
|
69
|
+
/* Transform AST */
|
|
70
|
+
tsInstance.transform(
|
|
71
|
+
/* sourceFiles */ program.getSourceFiles().filter(sourceFile => rootFileNames.includes(sourceFile.fileName) && !sourceFile.fileName.includes("after_restfuncs-transformer")),
|
|
72
|
+
/* transformerFactoryOOP */ [ transformerFactoryOOP.asFunction ],
|
|
73
|
+
compilerOptions
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
transformerFactoryOOP.transformRunsDone.forEach(transformRun => {
|
|
77
|
+
if(transformRun.result.patches.length > 0) { // wants to make changes to the file ?
|
|
78
|
+
/* Render modified files and create new SourceFiles for them to use in host's cache */
|
|
79
|
+
const sourceFile = transformRun.sourceFile;
|
|
80
|
+
const sourceText = fs.readFileSync(sourceFile.fileName, {encoding: "utf8"});
|
|
81
|
+
const patchedSourceText = transformRun.result.applyPatches(sourceText);
|
|
82
|
+
if((config as any).debug) {
|
|
83
|
+
// Write content to file so you can better inspect it.
|
|
84
|
+
const newFileName = sourceFile.fileName.replace(/\.ts$/,".after_restfuncs-transformer.tmp");
|
|
85
|
+
if(newFileName === sourceFile.fileName) {
|
|
86
|
+
throw new Error("invalid file name")
|
|
87
|
+
}
|
|
88
|
+
fs.writeFileSync(newFileName, patchedSourceText, {encoding: "utf8"});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
// Insert patchedSourceText into host's cache:
|
|
93
|
+
const newSourceFile = tsInstance.createSourceFile(sourceFile.fileName, patchedSourceText, sourceFile.languageVersion);
|
|
94
|
+
newSourceFile.version = `${sourceFile.version}_restfuncs_${transformerVersion.major}.${transformerVersion.feature}`;
|
|
95
|
+
compilerHost.fileCache.set(sourceFile.fileName, newSourceFile);
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
/* Re-create Program instance */
|
|
102
|
+
return tsInstance.createProgram(rootFileNames, compilerOptions, compilerHost);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "restfuncs-transformer",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.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,10 @@
|
|
|
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
|
+
"test": "npm run build && jest --runInBand --testPathIgnorePatterns=\".*\\.ts\" --testNamePattern=\".*\"",
|
|
17
|
+
"dev:playgrund": "tsx devPlayground.ts"
|
|
16
18
|
},
|
|
17
19
|
"exports": {
|
|
18
20
|
".": {
|
|
@@ -20,10 +22,16 @@
|
|
|
20
22
|
}
|
|
21
23
|
},
|
|
22
24
|
"dependencies": {
|
|
23
|
-
"typescript
|
|
25
|
+
"typescript": "5.0 || 5.1 || 5.3",
|
|
26
|
+
"restfuncs-common": "^3.1.0"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"restfuncs-server": ">=2"
|
|
24
30
|
},
|
|
25
31
|
"devDependencies": {
|
|
26
|
-
"
|
|
27
|
-
"
|
|
32
|
+
"@types/ts-expose-internals": "npm:ts-expose-internals@5.3.3",
|
|
33
|
+
"rimraf": "=5.0.5",
|
|
34
|
+
"ts-jest": "^29.0.3",
|
|
35
|
+
"@types/jest": "^29.2.2"
|
|
28
36
|
}
|
|
29
37
|
}
|
package/readme.md
CHANGED
|
@@ -1,4 +1,71 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# restfuncs-transformer
|
|
2
|
+
This transformer prepares `ServerSession` subclasses for Typia and adds jsDoc info for the (upcoming) API browser.
|
|
3
3
|
|
|
4
|
-
|
|
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
|
+
## Debugging parameters
|
|
8
|
+
Insert these into the `{ "transform": "restfuncs-transformer", ...}` block.
|
|
9
|
+
- debug: Set to true to dump the output of transformed **and affected** files each into a file named ...after_restfuncs-transformer.tmp
|
|
10
|
+
- pretty: Pretty print the output (normally it is squeezed into one line to preserve line numbers and keep source maps intact)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## How the transformer chain works
|
|
14
|
+
Here is, how the restfuncs-transformer, typia transformer and typescript-rtti work together:
|
|
15
|
+
1. The `restfuncs-transformer` scans for all `@remote` methods and, at the bottom of inside their class, it adds the following code (here for):
|
|
16
|
+
````typescript
|
|
17
|
+
@remote
|
|
18
|
+
async myMethod(param1: string, someCallbackFn: (p:number) => Promise<string>);
|
|
19
|
+
````
|
|
20
|
+
|
|
21
|
+
results in adding this to the class body:
|
|
22
|
+
|
|
23
|
+
````typescript
|
|
24
|
+
import _rf_typia from "typia";
|
|
25
|
+
/**
|
|
26
|
+
* Code is generated by the restfuncs-transformer during compile-time
|
|
27
|
+
*/
|
|
28
|
+
static getRemoteMethodsMeta(): (typeof this.type_remoteMethodsMeta) {
|
|
29
|
+
this.__hello_developer__make_sure_your_class_is_a_subclass_of_ServerSession // Attempt to give a friendly error message when this is not the case. Otherwise the previous line would fail and leaves the user wondering.
|
|
30
|
+
type CallbackPlaceholder<DECL_INDEX extends number> = string & _rf_typia.tags.TagBase<{kind: "callbackFn", target: "string", value: DECL_INDEX, validate: `onValidateCallbackArg($input, ${DECL_INDEX})`}>; // Create a [custom tag](https://typia.io/docs/validators/tags/#customization), that can be used via `CallbackPlaceholder<n>`. During validation, this tag then calls onValidateCallbackArg.
|
|
31
|
+
const result= {
|
|
32
|
+
transformerVersion: {major: XX, feature: XX },
|
|
33
|
+
instanceMethods: {
|
|
34
|
+
"myMethod": {
|
|
35
|
+
arguments: {
|
|
36
|
+
validateEquals: (args: unknown) => _rf_typia.validateEquals<[param1: string, someCallback: (p:number) => Promise<string>]>(args),
|
|
37
|
+
validatePrune: ...,
|
|
38
|
+
withPlaceholders: {
|
|
39
|
+
validateEquals: (args: unknown, onValidateCallbackArg: (placeholderId: string, declarationIndex: number) => boolean) => _rf_typia.validateEquals<[param1: string, someCallback: CallbackPlaceholder<0>]>(args),
|
|
40
|
+
...
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
result: {
|
|
44
|
+
validateEquals: (value: unknown) => _rf_typia.validateEquals<Awaited<ReturnType<typeof this.prototype["myMethod"]>>>(value),
|
|
45
|
+
...
|
|
46
|
+
},
|
|
47
|
+
callbacks: [{ // here for the `someCallbackFn: (p:number) => Promise<string>` declaration
|
|
48
|
+
arguments: {
|
|
49
|
+
validateEquals: (args: unknown) => _rf_typia.validateEquals<[p: number]>(args),
|
|
50
|
+
validatePrune: ...
|
|
51
|
+
},
|
|
52
|
+
awaitedResult: {
|
|
53
|
+
validateEquals: (value: unknown) => _rf_typia.validateEquals<string>(value),
|
|
54
|
+
...
|
|
55
|
+
}
|
|
56
|
+
}],
|
|
57
|
+
jsDoc: {
|
|
58
|
+
comment: "text with <strong>markup</strong>",
|
|
59
|
+
params: {a: "text..."},
|
|
60
|
+
tags: [{name:"returns", comment: "text..."}]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return result; // Code style note for this line: Why not do `return {...}` directly ? This tiny difference allows for extra properties which ensure backward compatibility with older "restfuncs-server" packages.
|
|
67
|
+
}
|
|
68
|
+
````
|
|
69
|
+
|
|
70
|
+
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).
|
|
71
|
+
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,60 @@
|
|
|
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 declare class TextPatch {
|
|
50
|
+
/**
|
|
51
|
+
* character index (not byte index) => content to insert
|
|
52
|
+
*/
|
|
53
|
+
patches: {
|
|
54
|
+
position: number;
|
|
55
|
+
contentToInsert: string;
|
|
56
|
+
}[];
|
|
57
|
+
applyPatches(content: string): string;
|
|
58
|
+
}
|
|
59
|
+
export {};
|
|
60
|
+
//# 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;AAED,qBAAa,SAAS;IAClB;;OAEG;IACH,OAAO,EAAE;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAC,EAAE,CAAM;IAC5D,YAAY,CAAC,OAAO,EAAE,MAAM;CAgB/B"}
|
|
@@ -0,0 +1,126 @@
|
|
|
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.TextPatch = 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
|
+
class TextPatch {
|
|
105
|
+
constructor() {
|
|
106
|
+
/**
|
|
107
|
+
* character index (not byte index) => content to insert
|
|
108
|
+
*/
|
|
109
|
+
this.patches = [];
|
|
110
|
+
}
|
|
111
|
+
applyPatches(content) {
|
|
112
|
+
this.patches.sort((a, b) => a.position - b.position);
|
|
113
|
+
let result = "";
|
|
114
|
+
let emittedTil = 0;
|
|
115
|
+
for (const patch of this.patches) {
|
|
116
|
+
// emit the stuff before the patch
|
|
117
|
+
result += content.slice(emittedTil, patch.position);
|
|
118
|
+
emittedTil = patch.position;
|
|
119
|
+
result += patch.contentToInsert;
|
|
120
|
+
}
|
|
121
|
+
result += content.slice(emittedTil); // emit the rest
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
exports.TextPatch = TextPatch;
|
|
126
|
+
//# sourceMappingURL=transformerUtil.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transformerUtil.js","sourceRoot":"","sources":["transformerUtil.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transformerUtil.test.d.ts","sourceRoot":"","sources":["transformerUtil.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const transformerUtil_1 = require("./transformerUtil");
|
|
4
|
+
jest.setTimeout(60 * 60 * 1000); // Increase timeout to 1h to make debugging possible
|
|
5
|
+
test('TextPatch', () => {
|
|
6
|
+
const content = 'abc><de><fg';
|
|
7
|
+
const patch = new transformerUtil_1.TextPatch();
|
|
8
|
+
patch.patches = [{ position: 4, contentToInsert: "_insert1_" }, { position: 8, contentToInsert: "_insert2_" }];
|
|
9
|
+
expect(patch.applyPatches(content)).toBe('abc>_insert1_<de>_insert2_<fg');
|
|
10
|
+
});
|
|
11
|
+
//# sourceMappingURL=transformerUtil.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transformerUtil.test.js","sourceRoot":"","sources":["transformerUtil.test.ts"],"names":[],"mappings":";;AAAA,uDAA4C;AAE5C,IAAI,CAAC,UAAU,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,oDAAoD;AAGrF,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;IACnB,MAAM,OAAO,GAAC,aAAa,CAAA;IAC3B,MAAM,KAAK,GAAG,IAAI,2BAAS,EAAE,CAAC;IAC9B,KAAK,CAAC,OAAO,GAAG,CAAC,EAAC,QAAQ,EAAE,CAAC,EAAE,eAAe,EAAE,WAAW,EAAC,EAAE,EAAC,QAAQ,EAAE,CAAC,EAAE,eAAe,EAAE,WAAW,EAAC,CAAC,CAAC;IAC3G,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;AAC9E,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {TextPatch} from "./transformerUtil";
|
|
2
|
+
|
|
3
|
+
jest.setTimeout(60 * 60 * 1000); // Increase timeout to 1h to make debugging possible
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
test('TextPatch', () => {
|
|
7
|
+
const content='abc><de><fg'
|
|
8
|
+
const patch = new TextPatch();
|
|
9
|
+
patch.patches = [{position: 4, contentToInsert: "_insert1_"}, {position: 8, contentToInsert: "_insert2_"}];
|
|
10
|
+
expect(patch.applyPatches(content)).toBe('abc>_insert1_<de>_insert2_<fg');
|
|
11
|
+
});
|
|
12
|
+
|
|
@@ -0,0 +1,127 @@
|
|
|
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
|
+
}
|
|
105
|
+
|
|
106
|
+
export class TextPatch {
|
|
107
|
+
/**
|
|
108
|
+
* character index (not byte index) => content to insert
|
|
109
|
+
*/
|
|
110
|
+
patches: {position: number, contentToInsert: string}[] = [];
|
|
111
|
+
applyPatches(content: string) {
|
|
112
|
+
this.patches.sort((a,b) => a.position - b.position);
|
|
113
|
+
let result = "";
|
|
114
|
+
let emittedTil = 0;
|
|
115
|
+
for(const patch of this.patches) {
|
|
116
|
+
// emit the stuff before the patch
|
|
117
|
+
result+=content.slice(emittedTil, patch.position);
|
|
118
|
+
emittedTil = patch.position;
|
|
119
|
+
|
|
120
|
+
result+=patch.contentToInsert;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
result+= content.slice(emittedTil);// emit the rest
|
|
124
|
+
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
}
|