@esportsplus/typescript 0.10.2 → 0.12.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/build/transformer/constants.d.ts +4 -0
- package/build/transformer/constants.js +4 -0
- package/build/transformer/index.d.ts +14 -0
- package/build/transformer/index.js +158 -0
- package/build/transformer/program.d.ts +6 -0
- package/build/transformer/program.js +33 -0
- package/build/transformer/types.d.ts +23 -0
- package/build/transformer/types.js +1 -0
- package/package.json +7 -1
- package/src/transformer/constants.ts +8 -0
- package/src/transformer/index.ts +258 -0
- package/src/transformer/program.ts +54 -0
- package/src/transformer/types.ts +38 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import type { ImportModification, NodeMatch, QuickCheckPattern, Replacement, VisitorCallback, VisitorPredicate } from './types.js';
|
|
3
|
+
import program from './program.js';
|
|
4
|
+
declare const addImport: (code: string, module: string, specifiers: string[]) => string;
|
|
5
|
+
declare const applyReplacements: (code: string, replacements: Replacement[]) => string;
|
|
6
|
+
declare const applyReplacementsReverse: (code: string, replacements: Replacement[]) => string;
|
|
7
|
+
declare const collectNodes: <T>(sourceFile: ts.SourceFile, predicate: (node: ts.Node) => T | null) => NodeMatch<T>[];
|
|
8
|
+
declare const mightNeedTransform: (code: string, check: QuickCheckPattern) => boolean;
|
|
9
|
+
declare const uid: (prefix?: string) => string;
|
|
10
|
+
declare const updateImports: (code: string, modification: ImportModification) => string;
|
|
11
|
+
declare const visitAst: <T>(sourceFile: ts.SourceFile, callback: VisitorCallback<T>, state: T, predicate?: VisitorPredicate) => T;
|
|
12
|
+
declare const visitAstWithDepth: <T>(sourceFile: ts.SourceFile, callback: (node: ts.Node, depth: number, state: T) => void, state: T, depthTrigger: (node: ts.Node) => boolean) => T;
|
|
13
|
+
export { addImport, applyReplacements, applyReplacementsReverse, collectNodes, mightNeedTransform, program, uid, updateImports, visitAst, visitAstWithDepth };
|
|
14
|
+
export type { ImportModification, NodeMatch, QuickCheckPattern, Replacement, VisitorCallback, VisitorPredicate };
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { uuid } from '@esportsplus/utilities';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
import { BRACES_CONTENT_REGEX, REGEX_ESCAPE_PATTERN, UUID_DASH_REGEX } from './constants.js';
|
|
4
|
+
import program from './program.js';
|
|
5
|
+
function buildImportRegex(escapedModule) {
|
|
6
|
+
return new RegExp(`(import\\s*\\{[^}]*\\}\\s*from\\s*['"]${escapedModule}['"])`);
|
|
7
|
+
}
|
|
8
|
+
function mergeAndSort(a, b) {
|
|
9
|
+
let combined = new Array(a.length + b.size), idx = 0, n = a.length;
|
|
10
|
+
for (let i = 0; i < n; i++) {
|
|
11
|
+
if (a[i]) {
|
|
12
|
+
combined[idx++] = a[i];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
for (let item of b) {
|
|
16
|
+
if (item) {
|
|
17
|
+
combined[idx++] = item;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
combined.length = idx;
|
|
21
|
+
combined.sort();
|
|
22
|
+
return combined.join(', ');
|
|
23
|
+
}
|
|
24
|
+
function parseSpecifiers(str) {
|
|
25
|
+
let parts = str.split(','), result = new Set();
|
|
26
|
+
for (let i = 0, n = parts.length; i < n; i++) {
|
|
27
|
+
let trimmed = parts[i].trim();
|
|
28
|
+
if (trimmed) {
|
|
29
|
+
result.add(trimmed);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
function updateImportsWithRegex(code, specifiers, importRegex) {
|
|
35
|
+
let match = code.match(importRegex);
|
|
36
|
+
if (!match) {
|
|
37
|
+
return code;
|
|
38
|
+
}
|
|
39
|
+
let bracesMatch = match[1].match(BRACES_CONTENT_REGEX), existing = bracesMatch?.[1] ? parseSpecifiers(bracesMatch[1]) : new Set(), toAdd = [];
|
|
40
|
+
for (let spec of specifiers) {
|
|
41
|
+
if (!existing.has(spec)) {
|
|
42
|
+
toAdd.push(spec);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (toAdd.length === 0) {
|
|
46
|
+
return code;
|
|
47
|
+
}
|
|
48
|
+
return code.replace(match[1], match[1].replace(BRACES_CONTENT_REGEX, `{ ${mergeAndSort(toAdd, existing)} }`));
|
|
49
|
+
}
|
|
50
|
+
const addImport = (code, module, specifiers) => {
|
|
51
|
+
if (specifiers.length === 0) {
|
|
52
|
+
return code;
|
|
53
|
+
}
|
|
54
|
+
let regex = buildImportRegex(module.replace(REGEX_ESCAPE_PATTERN, '\\$&'));
|
|
55
|
+
if (regex.test(code)) {
|
|
56
|
+
return updateImportsWithRegex(code, new Set(specifiers), regex);
|
|
57
|
+
}
|
|
58
|
+
let adding = `import { ${specifiers.sort().join(', ')} } from '${module}';\n`, first = code.indexOf('import ');
|
|
59
|
+
if (first === -1) {
|
|
60
|
+
return adding + code;
|
|
61
|
+
}
|
|
62
|
+
return code.substring(0, first) + adding + code.substring(first);
|
|
63
|
+
};
|
|
64
|
+
const applyReplacements = (code, replacements) => {
|
|
65
|
+
if (replacements.length === 0) {
|
|
66
|
+
return code;
|
|
67
|
+
}
|
|
68
|
+
replacements.sort((a, b) => a.start - b.start);
|
|
69
|
+
let parts = [], pos = 0;
|
|
70
|
+
for (let i = 0, n = replacements.length; i < n; i++) {
|
|
71
|
+
let r = replacements[i];
|
|
72
|
+
if (r.start > pos) {
|
|
73
|
+
parts.push(code.substring(pos, r.start));
|
|
74
|
+
}
|
|
75
|
+
parts.push(r.newText);
|
|
76
|
+
pos = r.end;
|
|
77
|
+
}
|
|
78
|
+
if (pos < code.length) {
|
|
79
|
+
parts.push(code.substring(pos));
|
|
80
|
+
}
|
|
81
|
+
return parts.join('');
|
|
82
|
+
};
|
|
83
|
+
const applyReplacementsReverse = (code, replacements) => {
|
|
84
|
+
if (replacements.length === 0) {
|
|
85
|
+
return code;
|
|
86
|
+
}
|
|
87
|
+
replacements.sort((a, b) => b.start - a.start);
|
|
88
|
+
let result = code;
|
|
89
|
+
for (let i = 0, n = replacements.length; i < n; i++) {
|
|
90
|
+
let r = replacements[i];
|
|
91
|
+
result = result.substring(0, r.start) + r.newText + result.substring(r.end);
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
};
|
|
95
|
+
const collectNodes = (sourceFile, predicate) => {
|
|
96
|
+
let matches = [];
|
|
97
|
+
function visit(node) {
|
|
98
|
+
let data = predicate(node);
|
|
99
|
+
if (data !== null) {
|
|
100
|
+
matches.push({
|
|
101
|
+
data,
|
|
102
|
+
end: node.end,
|
|
103
|
+
node,
|
|
104
|
+
start: node.getStart(sourceFile)
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
ts.forEachChild(node, visit);
|
|
108
|
+
}
|
|
109
|
+
visit(sourceFile);
|
|
110
|
+
return matches;
|
|
111
|
+
};
|
|
112
|
+
const mightNeedTransform = (code, check) => {
|
|
113
|
+
if (check.regex) {
|
|
114
|
+
return check.regex.test(code);
|
|
115
|
+
}
|
|
116
|
+
if (check.patterns) {
|
|
117
|
+
for (let i = 0, n = check.patterns.length; i < n; i++) {
|
|
118
|
+
if (code.indexOf(check.patterns[i]) !== -1) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
};
|
|
125
|
+
const uid = (prefix) => {
|
|
126
|
+
return (prefix ? prefix + '_' : '_') + uuid().replace(UUID_DASH_REGEX, '_');
|
|
127
|
+
};
|
|
128
|
+
const updateImports = (code, modification) => {
|
|
129
|
+
let { module, specifiers } = modification;
|
|
130
|
+
if (specifiers.size === 0) {
|
|
131
|
+
return code;
|
|
132
|
+
}
|
|
133
|
+
let escapedModule = module.replace(REGEX_ESCAPE_PATTERN, '\\$&'), importRegex = buildImportRegex(escapedModule);
|
|
134
|
+
return updateImportsWithRegex(code, specifiers, importRegex);
|
|
135
|
+
};
|
|
136
|
+
const visitAst = (sourceFile, callback, state, predicate) => {
|
|
137
|
+
function visit(node) {
|
|
138
|
+
if (!predicate || predicate(node)) {
|
|
139
|
+
callback(node, state);
|
|
140
|
+
}
|
|
141
|
+
ts.forEachChild(node, visit);
|
|
142
|
+
}
|
|
143
|
+
visit(sourceFile);
|
|
144
|
+
return state;
|
|
145
|
+
};
|
|
146
|
+
const visitAstWithDepth = (sourceFile, callback, state, depthTrigger) => {
|
|
147
|
+
let depthStack = [0];
|
|
148
|
+
function visit(node) {
|
|
149
|
+
let depth = depthStack[depthStack.length - 1], nextDepth = depthTrigger(node) ? depth + 1 : depth;
|
|
150
|
+
callback(node, depth, state);
|
|
151
|
+
depthStack.push(nextDepth);
|
|
152
|
+
ts.forEachChild(node, visit);
|
|
153
|
+
depthStack.pop();
|
|
154
|
+
}
|
|
155
|
+
visit(sourceFile);
|
|
156
|
+
return state;
|
|
157
|
+
};
|
|
158
|
+
export { addImport, applyReplacements, applyReplacementsReverse, collectNodes, mightNeedTransform, program, uid, updateImports, visitAst, visitAstWithDepth };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
let cache = new Map();
|
|
4
|
+
function create(root) {
|
|
5
|
+
let tsconfig = ts.findConfigFile(root, ts.sys.fileExists, 'tsconfig.json');
|
|
6
|
+
if (!tsconfig) {
|
|
7
|
+
throw new Error('tsconfig.json not found');
|
|
8
|
+
}
|
|
9
|
+
let file = ts.readConfigFile(tsconfig, ts.sys.readFile);
|
|
10
|
+
if (file.error) {
|
|
11
|
+
throw new Error(`Error reading tsconfig.json: ${file.error.messageText}`);
|
|
12
|
+
}
|
|
13
|
+
let parsed = ts.parseJsonConfigFileContent(file.config, ts.sys, path.dirname(tsconfig));
|
|
14
|
+
if (parsed.errors.length > 0) {
|
|
15
|
+
throw new Error(`Error parsing tsconfig.json: ${parsed.errors[0].messageText}`);
|
|
16
|
+
}
|
|
17
|
+
return ts.createProgram({
|
|
18
|
+
options: parsed.options,
|
|
19
|
+
rootNames: parsed.fileNames
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const get = (root) => {
|
|
23
|
+
let program = cache.get(root);
|
|
24
|
+
if (!program) {
|
|
25
|
+
program = create(root);
|
|
26
|
+
cache.set(root, program);
|
|
27
|
+
}
|
|
28
|
+
return program;
|
|
29
|
+
};
|
|
30
|
+
const del = (root) => {
|
|
31
|
+
cache.delete(root);
|
|
32
|
+
};
|
|
33
|
+
export default { get, delete: del };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
type ImportModification = {
|
|
3
|
+
module: string;
|
|
4
|
+
specifiers: Set<string>;
|
|
5
|
+
};
|
|
6
|
+
type NodeMatch<T> = {
|
|
7
|
+
data: T;
|
|
8
|
+
end: number;
|
|
9
|
+
node: ts.Node;
|
|
10
|
+
start: number;
|
|
11
|
+
};
|
|
12
|
+
type QuickCheckPattern = {
|
|
13
|
+
patterns?: string[];
|
|
14
|
+
regex?: RegExp;
|
|
15
|
+
};
|
|
16
|
+
type Replacement = {
|
|
17
|
+
end: number;
|
|
18
|
+
newText: string;
|
|
19
|
+
start: number;
|
|
20
|
+
};
|
|
21
|
+
type VisitorCallback<T> = (node: ts.Node, state: T) => void;
|
|
22
|
+
type VisitorPredicate = (node: ts.Node) => boolean;
|
|
23
|
+
export type { ImportModification, NodeMatch, QuickCheckPattern, Replacement, VisitorCallback, VisitorPredicate };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
},
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"@esportsplus/cli-passthrough": "^0.0.12",
|
|
9
|
+
"@esportsplus/utilities": "^0.27.2",
|
|
10
|
+
"@types/node": "^25.0.3",
|
|
9
11
|
"tsc-alias": "^1.8.16",
|
|
10
12
|
"typescript": "^5.9.3"
|
|
11
13
|
},
|
|
@@ -14,6 +16,10 @@
|
|
|
14
16
|
"./tsconfig.browser.json": "./tsconfig.browser.json",
|
|
15
17
|
"./tsconfig.node.json": "./tsconfig.node.json",
|
|
16
18
|
"./tsconfig.package.json": "./tsconfig.package.json",
|
|
19
|
+
"./transformer": {
|
|
20
|
+
"types": "./build/transformer/index.d.ts",
|
|
21
|
+
"default": "./build/transformer/index.js"
|
|
22
|
+
},
|
|
17
23
|
".": {
|
|
18
24
|
"types": "./build/index.d.ts",
|
|
19
25
|
"default": "./build/index.js"
|
|
@@ -28,7 +34,7 @@
|
|
|
28
34
|
},
|
|
29
35
|
"type": "module",
|
|
30
36
|
"types": "build/index.d.ts",
|
|
31
|
-
"version": "0.
|
|
37
|
+
"version": "0.12.0",
|
|
32
38
|
"scripts": {
|
|
33
39
|
"build": "tsc && tsc-alias",
|
|
34
40
|
"-": "-"
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { uuid } from '@esportsplus/utilities';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
import { BRACES_CONTENT_REGEX, REGEX_ESCAPE_PATTERN, UUID_DASH_REGEX } from './constants.js';
|
|
4
|
+
import type { ImportModification, NodeMatch, QuickCheckPattern, Replacement, VisitorCallback, VisitorPredicate } from './types.js';
|
|
5
|
+
import program from './program';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
function buildImportRegex(escapedModule: string): RegExp {
|
|
9
|
+
return new RegExp(`(import\\s*\\{[^}]*\\}\\s*from\\s*['"]${escapedModule}['"])`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function mergeAndSort(a: string[], b: Set<string>): string {
|
|
13
|
+
let combined = new Array<string>(a.length + b.size),
|
|
14
|
+
idx = 0,
|
|
15
|
+
n = a.length;
|
|
16
|
+
|
|
17
|
+
for (let i = 0; i < n; i++) {
|
|
18
|
+
if (a[i]) {
|
|
19
|
+
combined[idx++] = a[i];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
for (let item of b) {
|
|
24
|
+
if (item) {
|
|
25
|
+
combined[idx++] = item;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
combined.length = idx;
|
|
30
|
+
combined.sort();
|
|
31
|
+
|
|
32
|
+
return combined.join(', ');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseSpecifiers(str: string): Set<string> {
|
|
36
|
+
let parts = str.split(','),
|
|
37
|
+
result = new Set<string>();
|
|
38
|
+
|
|
39
|
+
for (let i = 0, n = parts.length; i < n; i++) {
|
|
40
|
+
let trimmed = parts[i].trim();
|
|
41
|
+
|
|
42
|
+
if (trimmed) {
|
|
43
|
+
result.add(trimmed);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function updateImportsWithRegex(code: string, specifiers: Set<string>, importRegex: RegExp): string {
|
|
51
|
+
let match = code.match(importRegex);
|
|
52
|
+
|
|
53
|
+
if (!match) {
|
|
54
|
+
return code;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let bracesMatch = match[1].match(BRACES_CONTENT_REGEX),
|
|
58
|
+
existing = bracesMatch?.[1] ? parseSpecifiers(bracesMatch[1]) : new Set<string>(),
|
|
59
|
+
toAdd: string[] = [];
|
|
60
|
+
|
|
61
|
+
for (let spec of specifiers) {
|
|
62
|
+
if (!existing.has(spec)) {
|
|
63
|
+
toAdd.push(spec);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (toAdd.length === 0) {
|
|
68
|
+
return code;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return code.replace(
|
|
72
|
+
match[1],
|
|
73
|
+
match[1].replace(BRACES_CONTENT_REGEX, `{ ${mergeAndSort(toAdd, existing)} }`)
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
const addImport = (code: string, module: string, specifiers: string[]): string => {
|
|
79
|
+
if (specifiers.length === 0) {
|
|
80
|
+
return code;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let regex = buildImportRegex( module.replace(REGEX_ESCAPE_PATTERN, '\\$&') );
|
|
84
|
+
|
|
85
|
+
if (regex.test(code)) {
|
|
86
|
+
return updateImportsWithRegex(code, new Set(specifiers), regex);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let adding = `import { ${specifiers.sort().join(', ')} } from '${module}';\n`,
|
|
90
|
+
first = code.indexOf('import ');
|
|
91
|
+
|
|
92
|
+
if (first === -1) {
|
|
93
|
+
return adding + code;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return code.substring(0, first) + adding + code.substring(first);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const applyReplacements = (code: string, replacements: Replacement[]): string => {
|
|
100
|
+
if (replacements.length === 0) {
|
|
101
|
+
return code;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
replacements.sort((a, b) => a.start - b.start);
|
|
105
|
+
|
|
106
|
+
let parts: string[] = [],
|
|
107
|
+
pos = 0;
|
|
108
|
+
|
|
109
|
+
for (let i = 0, n = replacements.length; i < n; i++) {
|
|
110
|
+
let r = replacements[i];
|
|
111
|
+
|
|
112
|
+
if (r.start > pos) {
|
|
113
|
+
parts.push(code.substring(pos, r.start));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
parts.push(r.newText);
|
|
117
|
+
pos = r.end;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (pos < code.length) {
|
|
121
|
+
parts.push(code.substring(pos));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return parts.join('');
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const applyReplacementsReverse = (code: string, replacements: Replacement[]): string => {
|
|
128
|
+
if (replacements.length === 0) {
|
|
129
|
+
return code;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
replacements.sort((a, b) => b.start - a.start);
|
|
133
|
+
|
|
134
|
+
let result = code;
|
|
135
|
+
|
|
136
|
+
for (let i = 0, n = replacements.length; i < n; i++) {
|
|
137
|
+
let r = replacements[i];
|
|
138
|
+
|
|
139
|
+
result = result.substring(0, r.start) + r.newText + result.substring(r.end);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return result;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const collectNodes = <T>(sourceFile: ts.SourceFile, predicate: (node: ts.Node) => T | null): NodeMatch<T>[] => {
|
|
146
|
+
let matches: NodeMatch<T>[] = [];
|
|
147
|
+
|
|
148
|
+
function visit(node: ts.Node): void {
|
|
149
|
+
let data = predicate(node);
|
|
150
|
+
|
|
151
|
+
if (data !== null) {
|
|
152
|
+
matches.push({
|
|
153
|
+
data,
|
|
154
|
+
end: node.end,
|
|
155
|
+
node,
|
|
156
|
+
start: node.getStart(sourceFile)
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
ts.forEachChild(node, visit);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
visit(sourceFile);
|
|
164
|
+
|
|
165
|
+
return matches;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const mightNeedTransform = (code: string, check: QuickCheckPattern): boolean => {
|
|
169
|
+
if (check.regex) {
|
|
170
|
+
return check.regex.test(code);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (check.patterns) {
|
|
174
|
+
for (let i = 0, n = check.patterns.length; i < n; i++) {
|
|
175
|
+
if (code.indexOf(check.patterns[i]) !== -1) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return false;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const uid = (prefix?: string): string => {
|
|
185
|
+
return (prefix ? prefix + '_' : '_') + uuid().replace(UUID_DASH_REGEX, '_');
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const updateImports = (code: string, modification: ImportModification): string => {
|
|
189
|
+
let { module, specifiers } = modification;
|
|
190
|
+
|
|
191
|
+
if (specifiers.size === 0) {
|
|
192
|
+
return code;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let escapedModule = module.replace(REGEX_ESCAPE_PATTERN, '\\$&'),
|
|
196
|
+
importRegex = buildImportRegex(escapedModule);
|
|
197
|
+
|
|
198
|
+
return updateImportsWithRegex(code, specifiers, importRegex);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const visitAst = <T>(
|
|
202
|
+
sourceFile: ts.SourceFile,
|
|
203
|
+
callback: VisitorCallback<T>,
|
|
204
|
+
state: T,
|
|
205
|
+
predicate?: VisitorPredicate
|
|
206
|
+
): T => {
|
|
207
|
+
function visit(node: ts.Node): void {
|
|
208
|
+
if (!predicate || predicate(node)) {
|
|
209
|
+
callback(node, state);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
ts.forEachChild(node, visit);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
visit(sourceFile);
|
|
216
|
+
|
|
217
|
+
return state;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const visitAstWithDepth = <T>(
|
|
221
|
+
sourceFile: ts.SourceFile,
|
|
222
|
+
callback: (node: ts.Node, depth: number, state: T) => void,
|
|
223
|
+
state: T,
|
|
224
|
+
depthTrigger: (node: ts.Node) => boolean
|
|
225
|
+
): T => {
|
|
226
|
+
let depthStack: number[] = [0];
|
|
227
|
+
|
|
228
|
+
function visit(node: ts.Node): void {
|
|
229
|
+
let depth = depthStack[depthStack.length - 1],
|
|
230
|
+
nextDepth = depthTrigger(node) ? depth + 1 : depth;
|
|
231
|
+
|
|
232
|
+
callback(node, depth, state);
|
|
233
|
+
depthStack.push(nextDepth);
|
|
234
|
+
ts.forEachChild(node, visit);
|
|
235
|
+
depthStack.pop();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
visit(sourceFile);
|
|
239
|
+
|
|
240
|
+
return state;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
export {
|
|
245
|
+
addImport, applyReplacements, applyReplacementsReverse,
|
|
246
|
+
collectNodes,
|
|
247
|
+
mightNeedTransform,
|
|
248
|
+
program,
|
|
249
|
+
uid, updateImports,
|
|
250
|
+
visitAst, visitAstWithDepth
|
|
251
|
+
};
|
|
252
|
+
export type {
|
|
253
|
+
ImportModification,
|
|
254
|
+
NodeMatch,
|
|
255
|
+
QuickCheckPattern,
|
|
256
|
+
Replacement,
|
|
257
|
+
VisitorCallback, VisitorPredicate
|
|
258
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
let cache = new Map<string, ts.Program>();
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
function create(root: string): ts.Program {
|
|
9
|
+
let tsconfig = ts.findConfigFile(root, ts.sys.fileExists, 'tsconfig.json');
|
|
10
|
+
|
|
11
|
+
if (!tsconfig) {
|
|
12
|
+
throw new Error('tsconfig.json not found');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let file = ts.readConfigFile(tsconfig, ts.sys.readFile);
|
|
16
|
+
|
|
17
|
+
if (file.error) {
|
|
18
|
+
throw new Error(`Error reading tsconfig.json: ${file.error.messageText}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let parsed = ts.parseJsonConfigFileContent(
|
|
22
|
+
file.config,
|
|
23
|
+
ts.sys,
|
|
24
|
+
path.dirname(tsconfig)
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
if (parsed.errors.length > 0) {
|
|
28
|
+
throw new Error(`Error parsing tsconfig.json: ${parsed.errors[0].messageText}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return ts.createProgram({
|
|
32
|
+
options: parsed.options,
|
|
33
|
+
rootNames: parsed.fileNames
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
const get = (root: string): ts.Program => {
|
|
39
|
+
let program = cache.get(root);
|
|
40
|
+
|
|
41
|
+
if (!program) {
|
|
42
|
+
program = create(root);
|
|
43
|
+
cache.set(root, program);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return program;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const del = (root: string): void => {
|
|
50
|
+
cache.delete(root);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
export default { get, delete: del };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
type ImportModification = {
|
|
5
|
+
module: string;
|
|
6
|
+
specifiers: Set<string>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type NodeMatch<T> = {
|
|
10
|
+
data: T;
|
|
11
|
+
end: number;
|
|
12
|
+
node: ts.Node;
|
|
13
|
+
start: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type QuickCheckPattern = {
|
|
17
|
+
patterns?: string[];
|
|
18
|
+
regex?: RegExp;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type Replacement = {
|
|
22
|
+
end: number;
|
|
23
|
+
newText: string;
|
|
24
|
+
start: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type VisitorCallback<T> = (node: ts.Node, state: T) => void;
|
|
28
|
+
|
|
29
|
+
type VisitorPredicate = (node: ts.Node) => boolean;
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
export type {
|
|
33
|
+
ImportModification,
|
|
34
|
+
NodeMatch,
|
|
35
|
+
QuickCheckPattern,
|
|
36
|
+
Replacement,
|
|
37
|
+
VisitorCallback, VisitorPredicate
|
|
38
|
+
};
|