@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.
@@ -0,0 +1,4 @@
1
+ declare const BRACES_CONTENT_REGEX: RegExp;
2
+ declare const REGEX_ESCAPE_PATTERN: RegExp;
3
+ declare const UUID_DASH_REGEX: RegExp;
4
+ export { BRACES_CONTENT_REGEX, REGEX_ESCAPE_PATTERN, UUID_DASH_REGEX };
@@ -0,0 +1,4 @@
1
+ const BRACES_CONTENT_REGEX = /\{([^}]*)\}/;
2
+ const REGEX_ESCAPE_PATTERN = /[.*+?^${}()|[\]\\]/g;
3
+ const UUID_DASH_REGEX = /-/g;
4
+ export { BRACES_CONTENT_REGEX, REGEX_ESCAPE_PATTERN, UUID_DASH_REGEX };
@@ -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,6 @@
1
+ import ts from 'typescript';
2
+ declare const _default: {
3
+ get: (root: string) => ts.Program;
4
+ delete: (root: string) => void;
5
+ };
6
+ export default _default;
@@ -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.10.2",
37
+ "version": "0.12.0",
32
38
  "scripts": {
33
39
  "build": "tsc && tsc-alias",
34
40
  "-": "-"
@@ -0,0 +1,8 @@
1
+ const BRACES_CONTENT_REGEX = /\{([^}]*)\}/;
2
+
3
+ const REGEX_ESCAPE_PATTERN = /[.*+?^${}()|[\]\\]/g;
4
+
5
+ const UUID_DASH_REGEX = /-/g;
6
+
7
+
8
+ export { BRACES_CONTENT_REGEX, REGEX_ESCAPE_PATTERN, UUID_DASH_REGEX };
@@ -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
+ };